Skip to content

Commit

Permalink
WIP support deprecated configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
dmihalcik-virtru committed Jan 7, 2025
1 parent 3a81fc7 commit 23f9d81
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 9 deletions.
143 changes: 143 additions & 0 deletions service/internal/security/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package security

import (
"fmt"

"github.com/mitchellh/mapstructure"
)

// Copied from service/kas/access to avoid dep loop. To be removed.
type CurrentKeyFor struct {
Algorithm string `mapstructure:"alg"`
KID string `mapstructure:"kid"`
Private string `mapstructure:"private"`
Certificate string `mapstructure:"cert"`
Active bool `mapstructure:"active"`
Legacy bool `mapstructure:"legacy"`
}

// locate finds the index of the key in the Keyring slice.
func (k *KASConfigDupe) locate(kid string) (int, bool) {
for i, key := range k.Keyring {
if key.KID == kid {
return i, true
}
}
return -1, false
}

// For entries in keyring that appear with the same value for their KID field,
// consolidate them into a single entry. If one of the copies has 'Legacy' set, let the consolidated entry have 'Legacy' set.
// If one of the entries does not have `Legacy` set, set the value of `Active`.
func (k *KASConfigDupe) consolidate() {
seen := make(map[string]int)
for i, key := range k.Keyring {
if j, ok := seen[key.KID]; ok {
if key.Legacy {
k.Keyring[j].Legacy = true
} else {
k.Keyring[j].Active = key.Active
}
k.Keyring = append(k.Keyring[:i], k.Keyring[i+1:]...)
i--

Check failure on line 42 in service/internal/security/config.go

View workflow job for this annotation

GitHub Actions / go (service)

ineffectual assignment to i (ineffassign)
} else {
seen[key.KID] = i
}
}
}

// Deprecated
type KeyPairInfo struct {
// Valid algorithm. May be able to be derived from Private but it is better to just say it.
Algorithm string `mapstructure:"alg" json:"alg"`
// Key identifier. Should be short
KID string `mapstructure:"kid" json:"kid"`
// Implementation specific locator for private key;
// for 'standard' crypto service this is the path to a PEM file
Private string `mapstructure:"private" json:"private"`
// Optional locator for the corresponding certificate.
// If not found, only public key (derivable from Private) is available.
Certificate string `mapstructure:"cert" json:"cert"`
// Optional enumeration of intended usages of keypair
Usage string `mapstructure:"usage" json:"usage"`
// Optional long form description of key pair including purpose and life cycle information
Purpose string `mapstructure:"purpose" json:"purpose"`
}

// Deprecated
type StandardKeyInfo struct {
PrivateKeyPath string `mapstructure:"private_key_path" json:"private_key_path"`
PublicKeyPath string `mapstructure:"public_key_path" json:"public_key_path"`
}

// Deprecated
type CryptoConfig2024 struct {
Keys []KeyPairInfo `mapstructure:"keys" json:"keys"`
// Deprecated
RSAKeys map[string]StandardKeyInfo `mapstructure:"rsa,omitempty" json:"rsa,omitempty"`
// Deprecated
ECKeys map[string]StandardKeyInfo `mapstructure:"ec,omitempty" json:"ec,omitempty"`
}

type KASConfigDupe struct {
// Which keys are currently the default.
Keyring []CurrentKeyFor `mapstructure:"keyring" json:"keyring"`
// Deprecated
ECCertID string `mapstructure:"eccertid" json:"eccertid"`
// Deprecated
RSACertID string `mapstructure:"rsacertid" json:"rsacertid"`
}

func (c CryptoConfig2024) MarshalTo(within map[string]any) error {
var kasCfg KASConfigDupe
if err := mapstructure.Decode(within, &kasCfg); err != nil {
return fmt.Errorf("invalid kas cfg [%v] %w", within, err)
}
kasCfg.consolidate()
for kid, stdKeyInfo := range c.RSAKeys {
if i, ok := kasCfg.locate(kid); ok {
kasCfg.Keyring[i].Private = stdKeyInfo.PrivateKeyPath
kasCfg.Keyring[i].Certificate = stdKeyInfo.PublicKeyPath
continue
}
k := CurrentKeyFor{
Algorithm: "rsa:2048",
KID: kid,
Private: stdKeyInfo.PrivateKeyPath,
Certificate: stdKeyInfo.PublicKeyPath,
Active: true,
Legacy: true,
}
kasCfg.Keyring = append(kasCfg.Keyring, k)
}
for kid, stdKeyInfo := range c.ECKeys {
if i, ok := kasCfg.locate(kid); ok {
kasCfg.Keyring[i].Private = stdKeyInfo.PrivateKeyPath
kasCfg.Keyring[i].Certificate = stdKeyInfo.PublicKeyPath
continue
}
k := CurrentKeyFor{
Algorithm: "ec:secp256r1",
KID: kid,
Private: stdKeyInfo.PrivateKeyPath,
Certificate: stdKeyInfo.PublicKeyPath,
Active: true,
Legacy: true,
}
kasCfg.Keyring = append(kasCfg.Keyring, k)
}
for _, k := range c.Keys {
if i, ok := kasCfg.locate(k.KID); ok {
kasCfg.Keyring[i].Private = k.Private
kasCfg.Keyring[i].Certificate = k.Certificate
continue
}
kasCfg.Keyring = append(kasCfg.Keyring, CurrentKeyFor{
Algorithm: k.Algorithm,
KID: k.KID,
Private: k.Private,
Certificate: k.Certificate,
})
}
return nil
}
107 changes: 107 additions & 0 deletions service/internal/security/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package security

import (
"testing"

"github.com/mitchellh/mapstructure"
"github.com/stretchr/testify/assert"
)

func TestMarshalTo(t *testing.T) {
tests := []struct {
name string
config CryptoConfig2024
input map[string]any
expected KASConfigDupe
wantErr bool
}{
{
name: "older input (pre-2024, no legacy)",
config: CryptoConfig2024{
RSAKeys: map[string]StandardKeyInfo{
"rsa1": {PrivateKeyPath: "rsa1_private.pem", PublicKeyPath: "rsa1_public.pem"},
},
ECKeys: map[string]StandardKeyInfo{
"ec1": {PrivateKeyPath: "ec1_private.pem", PublicKeyPath: "ec1_public.pem"},
},
},
input: map[string]any{
"eccertid": "ec1",
"rsacertid": "rsa1",
},
expected: KASConfigDupe{
Keyring: []CurrentKeyFor{
{Algorithm: "rsa:2048", KID: "rsa1", Private: "rsa1_private.pem", Certificate: "rsa1_public.pem", Active: true, Legacy: true},
{Algorithm: "ec:secp256r1", KID: "ec1", Private: "ec1_private.pem", Certificate: "ec1_public.pem", Active: true, Legacy: true},
},
},
wantErr: false,
},
{
name: "older input (pre-2024, supports legacy)",
config: CryptoConfig2024{
RSAKeys: map[string]StandardKeyInfo{
"rsa1": {PrivateKeyPath: "rsa1_private.pem", PublicKeyPath: "rsa1_public.pem"},
},
ECKeys: map[string]StandardKeyInfo{
"ec1": {PrivateKeyPath: "ec1_private.pem", PublicKeyPath: "ec1_public.pem"},
},
},
input: map[string]any{},
expected: KASConfigDupe{
Keyring: []CurrentKeyFor{
{Algorithm: "rsa:2048", KID: "rsa1", Private: "rsa1_private.pem", Certificate: "rsa1_public.pem", Active: true, Legacy: true},
{Algorithm: "ec:secp256r1", KID: "ec1", Private: "ec1_private.pem", Certificate: "ec1_public.pem", Active: true, Legacy: true},
},
},
wantErr: false,
},
{
name: "older input (2024)",
config: CryptoConfig2024{
Keys: []KeyPairInfo{
{Algorithm: "rsa:2048", KID: "rsa1", Private: "rsa1_private.pem", Certificate: "rsa1_public.pem"},
{Algorithm: "ec:secp256r1", KID: "ec1", Private: "ec1_private.pem", Certificate: "ec1_public.pem"},
},
},
input: map[string]any{
"keyring": []map[string]any{
{"alg": "rsa:2048", "kid": "rsa1", "private": "rsa1_private.pem", "cert": "rsa1_public.pem", "active": true, "legacy": true},
{"alg": "ec:secp256r1", "kid": "ec1", "private": "ec1_private.pem", "cert": "ec1_public.pem", "active": true, "legacy": true},
},
},
expected: KASConfigDupe{
Keyring: []CurrentKeyFor{
{Algorithm: "rsa:2048", KID: "rsa1", Private: "rsa1_private.pem", Certificate: "rsa1_public.pem", Active: true, Legacy: true},
{Algorithm: "ec:secp256r1", KID: "ec1", Private: "ec1_private.pem", Certificate: "ec1_public.pem", Active: true, Legacy: true},
},
},
wantErr: false,
},
{
name: "Invalid input",
config: CryptoConfig2024{
RSAKeys: map[string]StandardKeyInfo{},
ECKeys: map[string]StandardKeyInfo{},
Keys: []KeyPairInfo{},
},
input: map[string]any{},
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.config.MarshalTo(tt.input)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)

Check failure on line 99 in service/internal/security/config_test.go

View workflow job for this annotation

GitHub Actions / go (service)

require-error: for error assertions use require (testifylint)
var result KASConfigDupe
err = mapstructure.Decode(tt.input, &result)
assert.NoError(t, err)

Check failure on line 102 in service/internal/security/config_test.go

View workflow job for this annotation

GitHub Actions / go (service)

require-error: for error assertions use require (testifylint)
assert.Equal(t, tt.expected, result)
}
})
}
}
17 changes: 11 additions & 6 deletions service/internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
sdkAudit "github.com/opentdf/platform/sdk/audit"
"github.com/opentdf/platform/service/internal/auth"
"github.com/opentdf/platform/service/internal/security"
"github.com/opentdf/platform/service/internal/server/memhttp"
"github.com/opentdf/platform/service/logger"
"github.com/opentdf/platform/service/logger/audit"
Expand All @@ -45,11 +46,13 @@ func (e Error) Error() string {

// Configurations for the server
type Config struct {
Auth auth.Config `mapstructure:"auth" json:"auth"`
GRPC GRPCConfig `mapstructure:"grpc" json:"grpc"`
TLS TLSConfig `mapstructure:"tls" json:"tls"`
CORS CORSConfig `mapstructure:"cors" json:"cors"`
WellKnownConfigRegister func(namespace string, config any) error `mapstructure:"-" json:"-"`
Auth auth.Config `mapstructure:"auth" json:"auth"`
GRPC GRPCConfig `mapstructure:"grpc" json:"grpc"`
// Deprecated: Specify all crypto details in the `services.kas.keyring` struct
security.CryptoConfig2024 `mapstructure:"cryptoProvider" json:"cryptoProvider"`
TLS TLSConfig `mapstructure:"tls" json:"tls"`
CORS CORSConfig `mapstructure:"cors" json:"cors"`
WellKnownConfigRegister func(namespace string, config any) error `mapstructure:"-" json:"-"`
// Port to listen on
Port int `mapstructure:"port" json:"port" default:"8080"`
Host string `mapstructure:"host,omitempty" json:"host"`
Expand Down Expand Up @@ -108,7 +111,8 @@ type ConnectRPC struct {
}

type OpenTDFServer struct {
AuthN *auth.Authentication
AuthN *auth.Authentication
*Config
GRPCGatewayMux *runtime.ServeMux
HTTPServer *http.Server
ConnectRPCInProcess *inProcessServer
Expand Down Expand Up @@ -196,6 +200,7 @@ func NewOpenTDFServer(config Config, logger *logger.Logger) (*OpenTDFServer, err

o := OpenTDFServer{
AuthN: authN,
Config: &config,
GRPCGatewayMux: grpcGatewayMux,
HTTPServer: httpServer,
ConnectRPC: connectRPC,
Expand Down
7 changes: 6 additions & 1 deletion service/pkg/server/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,13 @@ func startServices(ctx context.Context, cfg config.Config, otdf *server.OpenTDFS
}
}

svcConfig := cfg.Services[ns]
if ns == "kas" {

Check failure on line 171 in service/pkg/server/services.go

View workflow job for this annotation

GitHub Actions / go (service)

empty-block: this block is empty, you can remove it (revive)

}

err = svc.Start(ctx, serviceregistry.RegistrationParams{
Config: cfg.Services[svc.GetNamespace()],
Config: svcConfig,
Logger: svcLogger,
DBClient: svcDBClient,
SDK: client,
Expand Down
4 changes: 2 additions & 2 deletions service/pkg/serviceregistry/serviceregistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ type RegistrationParams struct {
// gRPC Inter Process Communication (IPC) between services. This ensures the services are
// communicating with each other by contract as well as supporting the various deployment models
// that OpenTDF supports.
SDK *sdk.SDK
*sdk.SDK
// Logger is the logger that can be used to log messages. This logger is scoped to the service
Logger *logger.Logger
*logger.Logger
trace.Tracer

////// The following functions are optional and intended to be called by the service //////
Expand Down

0 comments on commit 23f9d81

Please sign in to comment.