Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/sdcio/schema-server

go 1.25.0

replace github.com/openconfig/goyang v1.6.0 => github.com/sdcio/goyang v1.6.2-2
replace github.com/openconfig/goyang v1.6.0 => github.com/sdcio/goyang v1.6.2-2.0.20260608121857-4668a077cf72

require (
github.com/dgraph-io/badger/v4 v4.9.1
Expand All @@ -14,7 +14,7 @@ require (
github.com/olekukonko/tablewriter v1.1.4
github.com/openconfig/goyang v1.6.0
github.com/prometheus/client_golang v1.23.2
github.com/sdcio/sdc-protos v0.0.54
github.com/sdcio/sdc-protos v0.0.55-0.20260610090020-aeb8edf494c4
github.com/sirupsen/logrus v1.9.4
github.com/spf13/pflag v1.0.10
google.golang.org/grpc v1.81.1
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,12 @@ github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlT
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sdcio/goyang v1.6.2-2 h1:qfeUKBmoKpiKAruuEc3+V8wgHKP/n1jRDEnTy23knV8=
github.com/sdcio/goyang v1.6.2-2/go.mod h1:5WolITjek1NF8yrNERyVZ7jqjOClJTpO8p/+OwmETM4=
github.com/sdcio/goyang v1.6.2-2.0.20260608121857-4668a077cf72 h1:CRTnwetSF4zG2q3Weoj0gSlZAx0/VXggHGXDRD4X7jI=
github.com/sdcio/goyang v1.6.2-2.0.20260608121857-4668a077cf72/go.mod h1:5WolITjek1NF8yrNERyVZ7jqjOClJTpO8p/+OwmETM4=
github.com/sdcio/logger v0.0.3 h1:IFUbObObGry+S8lHGwOQKKRxJSuOphgRU/hxVhOdMOM=
github.com/sdcio/logger v0.0.3/go.mod h1:yWaOxK/G6vszjg8tKZiMqiEjlZouHsjFME4zSk+SAEA=
github.com/sdcio/sdc-protos v0.0.54 h1:1EbtU9ZbbJHFPOFGi5aW8Th79cuY9i+AJaP0ABVx8hw=
github.com/sdcio/sdc-protos v0.0.54/go.mod h1:YMLHbey0/aL1qtLW8csSYVPafsgnnn7aY54HkV5dbyQ=
github.com/sdcio/sdc-protos v0.0.55-0.20260610090020-aeb8edf494c4 h1:M1C1wzt2ni0fssvPEaMNPdHNKqTzBtT8VTT3/EzbrCE=
github.com/sdcio/sdc-protos v0.0.55-0.20260610090020-aeb8edf494c4/go.mod h1:NsvzvHnTonLcwQ/WNzxzBCauQmqxpuviaW0wh7Lkrts=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
Expand Down
13 changes: 13 additions & 0 deletions pkg/schema/leaf.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func (sc *Schema) leafFromYEntry(e *yang.Entry, withDesc bool) (*sdcpb.LeafSchem
IsState: isState(e),
Reference: make([]string, 0),
IfFeature: getIfFeature(e),
Sensitive: isSensitiveEntry(e),
}

if withDesc {
Expand Down Expand Up @@ -183,6 +184,18 @@ func getMustStatement(e *yang.Entry) []*sdcpb.MustStatement {
return rs
}

// isSensitiveEntry reports whether the goyang entry carries the
// sdcio-ext:sensitive extension, either directly on the leaf or propagated
// onto it via a deviate add/replace in a device-profile overlay YANG file.
func isSensitiveEntry(e *yang.Entry) bool {
for _, ext := range e.Exts {
if ext.Keyword == "sdcio-ext:sensitive" {
return true
}
}
return false
}

func getIfFeature(e *yang.Entry) []string {
ifFeatures, ok := e.Extra["if-feature"]
if !ok {
Expand Down
1 change: 1 addition & 0 deletions pkg/schema/leaf_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func (sc *Schema) leafListFromYEntry(e *yang.Entry, withDesc bool) (*sdcpb.LeafL
IsUserOrdered: false,
IfFeature: getIfFeature(e),
Defaults: e.DefaultValues(),
Sensitive: isSensitiveEntry(e),
}
if withDesc {
ll.Description = e.Description
Expand Down
147 changes: 147 additions & 0 deletions pkg/schema/leaf_sensitive_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright 2024 Nokia
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package schema

import (
"testing"

"github.com/openconfig/goyang/pkg/yang"
"github.com/sdcio/schema-server/pkg/config"
sdcpb "github.com/sdcio/sdc-protos/sdcpb"
)

// sensitiveSchema loads the YANG fixture under testdata/sensitive and returns
// the resulting Schema for use in sub-tests.
func sensitiveSchema(t *testing.T) *Schema {
t.Helper()
sc, err := NewSchema(&config.SchemaConfig{
Name: "sensitive-test",
Vendor: "test",
Version: "0.0.1",
Files: []string{
"testdata/sensitive",
},
})
if err != nil {
t.Fatalf("NewSchema: %v", err)
}
return sc
}

// TestIsSensitiveEntry verifies the low-level helper against hand-crafted
// yang.Entry objects — no YANG parsing required.
func TestIsSensitiveEntry(t *testing.T) {
tests := []struct {
name string
exts []*yang.Statement
want bool
}{
{
name: "nil exts → not sensitive",
exts: nil,
want: false,
},
{
name: "empty exts → not sensitive",
exts: []*yang.Statement{},
want: false,
},
{
name: "unrelated extension → not sensitive",
exts: []*yang.Statement{{Keyword: "other:tag"}},
want: false,
},
{
name: "sdcio-ext:sensitive → sensitive",
exts: []*yang.Statement{{Keyword: "sdcio-ext:sensitive"}},
want: true,
},
{
name: "mixed exts including sdcio-ext:sensitive → sensitive",
exts: []*yang.Statement{
{Keyword: "other:tag"},
{Keyword: "sdcio-ext:sensitive"},
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := &yang.Entry{Exts: tt.exts}
if got := isSensitiveEntry(e); got != tt.want {
t.Errorf("isSensitiveEntry() = %v, want %v", got, tt.want)
}
})
}
}

// TestLeafSchema_SensitiveFlag loads the YANG fixture and verifies that
// LeafSchema.Sensitive is set correctly for leaves with and without the
// sdcio-ext:sensitive extension.
func TestLeafSchema_SensitiveFlag(t *testing.T) {
sc := sensitiveSchema(t)

tests := []struct {
path []string
wantSens bool
wantField string
}{
{path: []string{"auth-password"}, wantSens: true, wantField: "auth-password (direct annotation)"},
{path: []string{"description"}, wantSens: false, wantField: "description (no annotation)"},
{path: []string{"plain-secret"}, wantSens: true, wantField: "plain-secret (deviate add in overlay)"},
}

for _, tt := range tests {
t.Run(tt.wantField, func(t *testing.T) {
e, err := sc.GetEntry(tt.path)
if err != nil {
t.Fatalf("GetEntry(%v): %v", tt.path, err)
}
elem, err := sc.SchemaElemFromYEntry(e, false)
if err != nil {
t.Fatalf("SchemaElemFromYEntry: %v", err)
}
leaf, ok := elem.Schema.(*sdcpb.SchemaElem_Field)
if !ok {
t.Fatalf("expected LeafSchema, got %T", elem.Schema)
}
if got := leaf.Field.GetSensitive(); got != tt.wantSens {
t.Errorf("LeafSchema.Sensitive = %v, want %v", got, tt.wantSens)
}
})
}
}

// TestLeafListSchema_SensitiveFlag verifies that LeafListSchema.Sensitive is
// set for leaf-lists annotated with sdcio-ext:sensitive.
func TestLeafListSchema_SensitiveFlag(t *testing.T) {
sc := sensitiveSchema(t)

e, err := sc.GetEntry([]string{"allowed-keys"})
if err != nil {
t.Fatalf("GetEntry: %v", err)
}
elem, err := sc.SchemaElemFromYEntry(e, false)
if err != nil {
t.Fatalf("SchemaElemFromYEntry: %v", err)
}
ll, ok := elem.Schema.(*sdcpb.SchemaElem_Leaflist)
if !ok {
t.Fatalf("expected LeafListSchema, got %T", elem.Schema)
}
if !ll.Leaflist.GetSensitive() {
t.Error("LeafListSchema.Sensitive = false, want true")
}
}
29 changes: 29 additions & 0 deletions pkg/schema/testdata/sensitive/device.yang
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module device {
yang-version 1.1;
namespace "urn:device";
prefix "device";

import sdcio-ext { prefix "sdcio-ext"; }

// Leaf with sdcio-ext:sensitive annotated directly.
leaf auth-password {
sdcio-ext:sensitive;
type string;
}

// Leaf-list with sdcio-ext:sensitive annotated directly.
leaf-list allowed-keys {
sdcio-ext:sensitive;
type string;
}

// Plain leaf — must NOT be marked sensitive.
leaf description {
type string;
}

// Plain leaf to be marked sensitive via deviate add in overlay.yang.
leaf plain-secret {
type string;
}
}
16 changes: 16 additions & 0 deletions pkg/schema/testdata/sensitive/overlay.yang
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module overlay {
yang-version 1.1;
namespace "urn:overlay";
prefix "overlay";

import device { prefix "device"; }
import sdcio-ext { prefix "sdcio-ext"; }

// Device-profile overlay: marks plain-secret sensitive via deviate add,
// exercising the ApplyDeviate Exts-propagation fix in sdcio/goyang.
deviation /device:plain-secret {
deviate add {
sdcio-ext:sensitive;
}
}
}
12 changes: 12 additions & 0 deletions pkg/schema/testdata/sensitive/sdcio-ext.yang
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module sdcio-ext {
yang-version 1.1;
namespace "urn:sdcio-ext";
prefix "sdcio-ext";

extension sensitive {
description
"Marks a leaf or leaf-list as containing sensitive data.
The northbound API redacts its value with '***' unless
include_sensitive is set on the request.";
}
}