Skip to content

Commit

Permalink
feat: add system level harden setting
Browse files Browse the repository at this point in the history
Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
  • Loading branch information
JeyJeyGao committed Oct 17, 2022
1 parent 01d5843 commit 204fdc4
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 28 deletions.
22 changes: 10 additions & 12 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package config
import (
"errors"
"io/fs"

"github.com/notaryproject/notation-go/dir"
)

// CertificateReference is a named file path.
Expand All @@ -21,10 +19,18 @@ func (c CertificateReference) Is(name string) bool {
// Config reflects the config.json file.
// Specification: https://github.com/notaryproject/notation/pull/76
type Config struct {
VerificationCertificates VerificationCertificates `json:"verificationCerts"`
InsecureRegistries []string `json:"insecureRegistries"`
VerificationCertificates VerificationCertificates `json:"verificationCerts,omitempty"`
InsecureRegistries []string `json:"insecureRegistries,omitempty"`
CredentialsStore string `json:"credsStore,omitempty"`
CredentialHelpers map[string]string `json:"credHelpers,omitempty"`

// EnvelopeType defines the envelope type for signing
EnvelopeType string `json:"envelopeType,omitempty"`

// Harden defines security level, default value is false
// if Harden is true, only read system level
// if harden is false, read user and system level
Harden bool `json:"harden,omitempty"`
}

// VerificationCertificates is a collection of public certs used for verification.
Expand All @@ -45,15 +51,7 @@ func (c *Config) Save(path string) error {
}

// LoadConfig reads the config from file or return a default config if not found.
//
// if `path` is an empty string, it uses built-in config.json directory, including
// user level and system level.
func LoadConfig(path string) (*Config, error) {
// set default path
if path == "" {
path = dir.Path.Config(dir.UnionLevel)
}

// load config
var config Config
err := load(path, &config)
Expand Down
1 change: 1 addition & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var sampleConfig = &Config{
InsecureRegistries: []string{
"registry.wabbit-networks.io",
},
EnvelopeType: "jws",
}

func TestLoadFile(t *testing.T) {
Expand Down
10 changes: 0 additions & 10 deletions config/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package config
import (
"errors"
"io/fs"

"github.com/notaryproject/notation-go/dir"
)

// X509KeyPair contains the paths of a public/private key pair files.
Expand Down Expand Up @@ -41,13 +39,7 @@ type SigningKeys struct {
}

// Save config to file.
//
// if `path` is an empty string, it uses built-in user level signingkey.json directory.
func (s *SigningKeys) Save(path string) error {
// set default path
if path == "" {
path = dir.Path.SigningKeyConfig()
}
return save(path, s)
}

Expand All @@ -58,8 +50,6 @@ func NewSigningKeys() *SigningKeys {

// LoadSigningKeys reads the config from file
// or return a default config if not found.
//
// if `path` is an empty string, it uses built-in user level signingkey.json directory.
func LoadSigningKeys(path string) (*SigningKeys, error) {
// load signingkeys config
var config SigningKeys
Expand Down
3 changes: 2 additions & 1 deletion config/testdata/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
},
"insecureRegistries": [
"registry.wabbit-networks.io"
]
],
"envelopeType": "jws"
}
21 changes: 21 additions & 0 deletions dir/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"os"
"path/filepath"
"runtime"

"github.com/notaryproject/notation-go/config"
)

const (
Expand Down Expand Up @@ -32,10 +34,16 @@ var (

// Path is a PathManager pointer
Path *PathManager

// harden defines security level, default value is false
// if Harden is true, only read system level
// if harden is false, read user and system level
harden bool
)

func init() {
loadPath()
loadSettings()
}

// loadPath function defines the directory for
Expand Down Expand Up @@ -99,3 +107,16 @@ func loadPath() {
),
}
}

// loadSettings loads the required settings for directory structure
func loadSettings() {
// load harden setting
configPath := filepath.Join(SystemConfig, ConfigFile)
cfg, err := config.LoadConfig(configPath)
// system level read permission is required. It is insecure to bypass
// reading config, so any error should panic.
if err != nil {
panic(err)
}
harden = cfg.Harden
}
21 changes: 21 additions & 0 deletions dir/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ type UnionDirFS interface {
fs.ReadDirFS
// GetPath returns the path of the named file or directory under the FS.
GetPath(elem ...string) (string, error)

// ListAllPath returns all available paths of the named file or directory
// under the FS.
ListAllPath(elem ...string) []string
}

// NewUnionDirFS create an unionDirFS by rootedFS.
Expand Down Expand Up @@ -112,6 +116,23 @@ func (u unionDirFS) GetPath(elem ...string) (string, error) {
}
}

// ListAllPath returns all available paths of the named file or directory under
// the unionDirFS
// ReadDir implements the ReadDirFS interface.
//
// if path doesn't exist, the result would be empty.
func (u unionDirFS) ListAllPath(elem ...string) []string {
pathSuffix := path.Join(elem...)
var paths []string
for _, dir := range u.Dirs {
_, err := fs.Stat(dir, pathSuffix)
if err == nil {
paths = append(paths, filepath.Join(dir.Root(), pathSuffix))
}
}
return paths
}

// ReadDir implements the ReadDirFS interface.
//
// Traverse all union directories and return all existing DirEntries.
Expand Down
40 changes: 35 additions & 5 deletions dir/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type DirLevel int

const (
// UnionLevel is the label to specify the directory to union user and
// system level, and user level has higher priority than system level.
// system level while user level has higher priority than system level.
// [directory spec]: https://github.com/notaryproject/notation/blob/main/specs/directory.md#category
UnionLevel DirLevel = iota

Expand All @@ -53,6 +53,8 @@ const (
UserLevel
)

var SecureLevel DirLevel

// PathManager contains the union directory file system and methods
// to access paths of notation.
type PathManager struct {
Expand All @@ -69,12 +71,17 @@ func checkError(err error) {
}

// Config returns the path of config.json based on named directory level.
//
// dirLevel will be overwritten based on security setting.
func (p *PathManager) Config(dirLevel DirLevel) string {
var (
path string
err error
)

// overwrite dirLevel based on security setting
dirLevel = secureDirLevel(dirLevel)

switch dirLevel {
case UnionLevel:
path, err = p.ConfigFS.GetPath(ConfigFile)
Expand Down Expand Up @@ -104,12 +111,17 @@ func (p *PathManager) SigningKeyConfig() string {

// TrustPolicy returns the path of trustpolicy.json file based on named
// directory level.
//
// dirLevel will be overwritten based on security setting.
func (p *PathManager) TrustPolicy(dirLevel DirLevel) string {
var (
path string
err error
)

// overwrite dirLevel based on security setting
dirLevel = secureDirLevel(dirLevel)

switch dirLevel {
case UnionLevel:
path, err = p.ConfigFS.GetPath(TrustPolicyFile)
Expand All @@ -125,20 +137,27 @@ func (p *PathManager) TrustPolicy(dirLevel DirLevel) string {

// TrustStore returns the path of x509 trust store certificate
// based on named directory level.
func (p *PathManager) TrustStore(dirLevel DirLevel, prefix, namedStore string) string {
//
// dirLevel will be overwritten based on security setting.
// elements are the sub-directories or file name under `truststore` directory.
func (p *PathManager) TrustStore(dirLevel DirLevel, elements ...string) string {
var (
path string
err error
)
// overwrite dirLevel based on security setting
dirLevel = secureDirLevel(dirLevel)

pathElements := append([]string{TrustStoreDir, "x509"}, elements...)

switch dirLevel {
case UnionLevel:
path, err = p.ConfigFS.GetPath(TrustStoreDir, "x509", prefix, namedStore)
path, err = p.ConfigFS.GetPath(pathElements...)
checkError(err)
case SystemLevel:
path = filepath.Join(SystemConfig, TrustStoreDir, "x509", prefix, namedStore)
path = filepath.Join(append([]string{SystemConfig}, pathElements...)...)
case UserLevel:
path = filepath.Join(UserConfig, TrustStoreDir, "x509", prefix, namedStore)
path = filepath.Join(append([]string{UserConfig}, pathElements...)...)
}

return path
Expand Down Expand Up @@ -174,3 +193,14 @@ func (p *PathManager) CachedSignatureStoreDirPath() string {
checkError(err)
return path
}

// secureDirLevel checks the security requirement based on `Harden` field in
// system level config.json and returns the directory level satisfying the
// security requirement.
func secureDirLevel(dirLevel DirLevel) DirLevel {
// if Harden is true, only use system directory
if harden == true {
return SystemLevel
}
return dirLevel
}
56 changes: 56 additions & 0 deletions dir/path_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package dir

import (
"fmt"
"io/fs"
"os"
"path/filepath"
"testing"
"testing/fstest"

Expand Down Expand Up @@ -314,3 +317,56 @@ func TestPathManager_CachedSignatureStoreDirPath(t *testing.T) {
t.Fatal("get CachedSignatureStoreDir() failed.")
}
}

func TestSecureDirLevel(t *testing.T) {
// backup and restore system config path
systemConfigBak := SystemConfig
t.Cleanup(func() {
SystemConfig = systemConfigBak
})

// generate temp config
SystemConfig = t.TempDir()

setHarden := func(harden bool, fileMode fs.FileMode) {
configPath := filepath.Join(SystemConfig, "config.json")
os.Remove(configPath)
f, err := os.OpenFile(configPath, os.O_CREATE|os.O_RDWR, fileMode)
if err != nil {
t.Fatal(err)
}
f.WriteString(fmt.Sprintf(`{"Harden": %v}`, harden))
f.Close()
loadSettings()
}

t.Run("harden is false", func(t *testing.T) {
setHarden(false, 0644)
dirLevel := secureDirLevel(UserLevel)
if dirLevel != UserLevel {
t.Fatalf("want dirLevel: %v, got dirLevel: %v", UserLevel, dirLevel)
}
})

t.Run("harden is tue", func(t *testing.T) {
setHarden(true, 0644)
dirLevel := secureDirLevel(UserLevel)
if dirLevel != SystemLevel {
t.Fatalf("want dirLevel: %v, got dirLevel: %v", SystemLevel, dirLevel)
}
})

t.Run("config permission error", func(t *testing.T) {
defer func() {
if d := recover(); d != nil {
return
}
}()
setHarden(true, 0000)
dirLevel := secureDirLevel(UserLevel)
if dirLevel != SystemLevel {
t.Fatalf("want dirLevel: %v, got dirLevel: %v", SystemLevel, dirLevel)
}
t.Fatal("should panic")
})
}

0 comments on commit 204fdc4

Please sign in to comment.