Skip to content

Commit 6f2bb64

Browse files
WIP support deprecated configuration
1 parent 9a8fd1d commit 6f2bb64

File tree

5 files changed

+269
-9
lines changed

5 files changed

+269
-9
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package security
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/mitchellh/mapstructure"
7+
)
8+
9+
// Copied from service/kas/access to avoid dep loop. To be removed.
10+
type CurrentKeyFor struct {
11+
Algorithm string `mapstructure:"alg"`
12+
KID string `mapstructure:"kid"`
13+
Private string `mapstructure:"private"`
14+
Certificate string `mapstructure:"cert"`
15+
Active bool `mapstructure:"active"`
16+
Legacy bool `mapstructure:"legacy"`
17+
}
18+
19+
// locate finds the index of the key in the Keyring slice.
20+
func (k *KASConfigDupe) locate(kid string) (int, bool) {
21+
for i, key := range k.Keyring {
22+
if key.KID == kid {
23+
return i, true
24+
}
25+
}
26+
return -1, false
27+
}
28+
29+
// For entries in keyring that appear with the same value for their KID field,
30+
// consolidate them into a single entry. If one of the copies has 'Legacy' set, let the consolidated entry have 'Legacy' set.
31+
// If one of the entries does not have `Legacy` set, set the value of `Active`.
32+
func (k *KASConfigDupe) consolidate() {
33+
seen := make(map[string]int)
34+
for i, key := range k.Keyring {
35+
if j, ok := seen[key.KID]; ok {
36+
if key.Legacy {
37+
k.Keyring[j].Legacy = true
38+
} else {
39+
k.Keyring[j].Active = key.Active
40+
}
41+
k.Keyring = append(k.Keyring[:i], k.Keyring[i+1:]...)
42+
i--
43+
} else {
44+
seen[key.KID] = i
45+
}
46+
}
47+
}
48+
49+
// Deprecated
50+
type KeyPairInfo struct {
51+
// Valid algorithm. May be able to be derived from Private but it is better to just say it.
52+
Algorithm string `mapstructure:"alg" json:"alg"`
53+
// Key identifier. Should be short
54+
KID string `mapstructure:"kid" json:"kid"`
55+
// Implementation specific locator for private key;
56+
// for 'standard' crypto service this is the path to a PEM file
57+
Private string `mapstructure:"private" json:"private"`
58+
// Optional locator for the corresponding certificate.
59+
// If not found, only public key (derivable from Private) is available.
60+
Certificate string `mapstructure:"cert" json:"cert"`
61+
// Optional enumeration of intended usages of keypair
62+
Usage string `mapstructure:"usage" json:"usage"`
63+
// Optional long form description of key pair including purpose and life cycle information
64+
Purpose string `mapstructure:"purpose" json:"purpose"`
65+
}
66+
67+
// Deprecated
68+
type StandardKeyInfo struct {
69+
PrivateKeyPath string `mapstructure:"private_key_path" json:"private_key_path"`
70+
PublicKeyPath string `mapstructure:"public_key_path" json:"public_key_path"`
71+
}
72+
73+
// Deprecated
74+
type CryptoConfig2024 struct {
75+
Keys []KeyPairInfo `mapstructure:"keys" json:"keys"`
76+
// Deprecated
77+
RSAKeys map[string]StandardKeyInfo `mapstructure:"rsa,omitempty" json:"rsa,omitempty"`
78+
// Deprecated
79+
ECKeys map[string]StandardKeyInfo `mapstructure:"ec,omitempty" json:"ec,omitempty"`
80+
}
81+
82+
type KASConfigDupe struct {
83+
// Which keys are currently the default.
84+
Keyring []CurrentKeyFor `mapstructure:"keyring" json:"keyring"`
85+
// Deprecated
86+
ECCertID string `mapstructure:"eccertid" json:"eccertid"`
87+
// Deprecated
88+
RSACertID string `mapstructure:"rsacertid" json:"rsacertid"`
89+
}
90+
91+
func (c CryptoConfig2024) MarshalTo(within map[string]any) error {
92+
var kasCfg KASConfigDupe
93+
if err := mapstructure.Decode(within, &kasCfg); err != nil {
94+
return fmt.Errorf("invalid kas cfg [%v] %w", within, err)
95+
}
96+
kasCfg.consolidate()
97+
for kid, stdKeyInfo := range c.RSAKeys {
98+
if i, ok := kasCfg.locate(kid); ok {
99+
kasCfg.Keyring[i].Private = stdKeyInfo.PrivateKeyPath
100+
kasCfg.Keyring[i].Certificate = stdKeyInfo.PublicKeyPath
101+
continue
102+
}
103+
k := CurrentKeyFor{
104+
Algorithm: "rsa:2048",
105+
KID: kid,
106+
Private: stdKeyInfo.PrivateKeyPath,
107+
Certificate: stdKeyInfo.PublicKeyPath,
108+
Active: true,
109+
Legacy: true,
110+
}
111+
kasCfg.Keyring = append(kasCfg.Keyring, k)
112+
}
113+
for kid, stdKeyInfo := range c.ECKeys {
114+
if i, ok := kasCfg.locate(kid); ok {
115+
kasCfg.Keyring[i].Private = stdKeyInfo.PrivateKeyPath
116+
kasCfg.Keyring[i].Certificate = stdKeyInfo.PublicKeyPath
117+
continue
118+
}
119+
k := CurrentKeyFor{
120+
Algorithm: "ec:secp256r1",
121+
KID: kid,
122+
Private: stdKeyInfo.PrivateKeyPath,
123+
Certificate: stdKeyInfo.PublicKeyPath,
124+
Active: true,
125+
Legacy: true,
126+
}
127+
kasCfg.Keyring = append(kasCfg.Keyring, k)
128+
}
129+
for _, k := range c.Keys {
130+
if i, ok := kasCfg.locate(k.KID); ok {
131+
kasCfg.Keyring[i].Private = k.Private
132+
kasCfg.Keyring[i].Certificate = k.Certificate
133+
continue
134+
}
135+
kasCfg.Keyring = append(kasCfg.Keyring, CurrentKeyFor{
136+
Algorithm: k.Algorithm,
137+
KID: k.KID,
138+
Private: k.Private,
139+
Certificate: k.Certificate,
140+
})
141+
}
142+
return nil
143+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package security
2+
3+
import (
4+
"testing"
5+
6+
"github.com/mitchellh/mapstructure"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestMarshalTo(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
config CryptoConfig2024
14+
input map[string]any
15+
expected KASConfigDupe
16+
wantErr bool
17+
}{
18+
{
19+
name: "older input (pre-2024, no legacy)",
20+
config: CryptoConfig2024{
21+
RSAKeys: map[string]StandardKeyInfo{
22+
"rsa1": {PrivateKeyPath: "rsa1_private.pem", PublicKeyPath: "rsa1_public.pem"},
23+
},
24+
ECKeys: map[string]StandardKeyInfo{
25+
"ec1": {PrivateKeyPath: "ec1_private.pem", PublicKeyPath: "ec1_public.pem"},
26+
},
27+
},
28+
input: map[string]any{
29+
"eccertid": "ec1",
30+
"rsacertid": "rsa1",
31+
},
32+
expected: KASConfigDupe{
33+
Keyring: []CurrentKeyFor{
34+
{Algorithm: "rsa:2048", KID: "rsa1", Private: "rsa1_private.pem", Certificate: "rsa1_public.pem", Active: true, Legacy: true},
35+
{Algorithm: "ec:secp256r1", KID: "ec1", Private: "ec1_private.pem", Certificate: "ec1_public.pem", Active: true, Legacy: true},
36+
},
37+
},
38+
wantErr: false,
39+
},
40+
{
41+
name: "older input (pre-2024, supports legacy)",
42+
config: CryptoConfig2024{
43+
RSAKeys: map[string]StandardKeyInfo{
44+
"rsa1": {PrivateKeyPath: "rsa1_private.pem", PublicKeyPath: "rsa1_public.pem"},
45+
},
46+
ECKeys: map[string]StandardKeyInfo{
47+
"ec1": {PrivateKeyPath: "ec1_private.pem", PublicKeyPath: "ec1_public.pem"},
48+
},
49+
},
50+
input: map[string]any{},
51+
expected: KASConfigDupe{
52+
Keyring: []CurrentKeyFor{
53+
{Algorithm: "rsa:2048", KID: "rsa1", Private: "rsa1_private.pem", Certificate: "rsa1_public.pem", Active: true, Legacy: true},
54+
{Algorithm: "ec:secp256r1", KID: "ec1", Private: "ec1_private.pem", Certificate: "ec1_public.pem", Active: true, Legacy: true},
55+
},
56+
},
57+
wantErr: false,
58+
},
59+
{
60+
name: "older input (2024)",
61+
config: CryptoConfig2024{
62+
Keys: []KeyPairInfo{
63+
{Algorithm: "rsa:2048", KID: "rsa1", Private: "rsa1_private.pem", Certificate: "rsa1_public.pem"},
64+
{Algorithm: "ec:secp256r1", KID: "ec1", Private: "ec1_private.pem", Certificate: "ec1_public.pem"},
65+
},
66+
},
67+
input: map[string]any{
68+
"keyring": []map[string]any{
69+
{"alg": "rsa:2048", "kid": "rsa1", "private": "rsa1_private.pem", "cert": "rsa1_public.pem", "active": true, "legacy": true},
70+
{"alg": "ec:secp256r1", "kid": "ec1", "private": "ec1_private.pem", "cert": "ec1_public.pem", "active": true, "legacy": true},
71+
},
72+
},
73+
expected: KASConfigDupe{
74+
Keyring: []CurrentKeyFor{
75+
{Algorithm: "rsa:2048", KID: "rsa1", Private: "rsa1_private.pem", Certificate: "rsa1_public.pem", Active: true, Legacy: true},
76+
{Algorithm: "ec:secp256r1", KID: "ec1", Private: "ec1_private.pem", Certificate: "ec1_public.pem", Active: true, Legacy: true},
77+
},
78+
},
79+
wantErr: false,
80+
},
81+
{
82+
name: "Invalid input",
83+
config: CryptoConfig2024{
84+
RSAKeys: map[string]StandardKeyInfo{},
85+
ECKeys: map[string]StandardKeyInfo{},
86+
Keys: []KeyPairInfo{},
87+
},
88+
input: map[string]any{},
89+
wantErr: true,
90+
},
91+
}
92+
93+
for _, tt := range tests {
94+
t.Run(tt.name, func(t *testing.T) {
95+
err := tt.config.MarshalTo(tt.input)
96+
if tt.wantErr {
97+
assert.Error(t, err)
98+
} else {
99+
assert.NoError(t, err)
100+
var result KASConfigDupe
101+
err = mapstructure.Decode(tt.input, &result)
102+
assert.NoError(t, err)
103+
assert.Equal(t, tt.expected, result)
104+
}
105+
})
106+
}
107+
}

service/internal/server/server.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
2222
sdkAudit "github.com/opentdf/platform/sdk/audit"
2323
"github.com/opentdf/platform/service/internal/auth"
24+
"github.com/opentdf/platform/service/internal/security"
2425
"github.com/opentdf/platform/service/internal/server/memhttp"
2526
"github.com/opentdf/platform/service/logger"
2627
"github.com/opentdf/platform/service/logger/audit"
@@ -45,11 +46,13 @@ func (e Error) Error() string {
4546

4647
// Configurations for the server
4748
type Config struct {
48-
Auth auth.Config `mapstructure:"auth" json:"auth"`
49-
GRPC GRPCConfig `mapstructure:"grpc" json:"grpc"`
50-
TLS TLSConfig `mapstructure:"tls" json:"tls"`
51-
CORS CORSConfig `mapstructure:"cors" json:"cors"`
52-
WellKnownConfigRegister func(namespace string, config any) error `mapstructure:"-" json:"-"`
49+
Auth auth.Config `mapstructure:"auth" json:"auth"`
50+
GRPC GRPCConfig `mapstructure:"grpc" json:"grpc"`
51+
// Deprecated: Specify all crypto details in the `services.kas.keyring` struct
52+
security.CryptoConfig2024 `mapstructure:"cryptoProvider" json:"cryptoProvider"`
53+
TLS TLSConfig `mapstructure:"tls" json:"tls"`
54+
CORS CORSConfig `mapstructure:"cors" json:"cors"`
55+
WellKnownConfigRegister func(namespace string, config any) error `mapstructure:"-" json:"-"`
5356
// Port to listen on
5457
Port int `mapstructure:"port" json:"port" default:"8080"`
5558
Host string `mapstructure:"host,omitempty" json:"host"`
@@ -108,7 +111,8 @@ type ConnectRPC struct {
108111
}
109112

110113
type OpenTDFServer struct {
111-
AuthN *auth.Authentication
114+
AuthN *auth.Authentication
115+
*Config
112116
GRPCGatewayMux *runtime.ServeMux
113117
HTTPServer *http.Server
114118
ConnectRPCInProcess *inProcessServer
@@ -196,6 +200,7 @@ func NewOpenTDFServer(config Config, logger *logger.Logger) (*OpenTDFServer, err
196200

197201
o := OpenTDFServer{
198202
AuthN: authN,
203+
Config: &config,
199204
GRPCGatewayMux: grpcGatewayMux,
200205
HTTPServer: httpServer,
201206
ConnectRPC: connectRPC,

service/pkg/server/services.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,13 @@ func startServices(ctx context.Context, cfg config.Config, otdf *server.OpenTDFS
167167
}
168168
}
169169

170+
svcConfig := cfg.Services[ns]
171+
if ns == "kas" {
172+
173+
}
174+
170175
err = svc.Start(ctx, serviceregistry.RegistrationParams{
171-
Config: cfg.Services[svc.GetNamespace()],
176+
Config: svcConfig,
172177
Logger: svcLogger,
173178
DBClient: svcDBClient,
174179
SDK: client,

service/pkg/serviceregistry/serviceregistry.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ type RegistrationParams struct {
3737
// gRPC Inter Process Communication (IPC) between services. This ensures the services are
3838
// communicating with each other by contract as well as supporting the various deployment models
3939
// that OpenTDF supports.
40-
SDK *sdk.SDK
40+
*sdk.SDK
4141
// Logger is the logger that can be used to log messages. This logger is scoped to the service
42-
Logger *logger.Logger
42+
*logger.Logger
4343
trace.Tracer
4444

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

0 commit comments

Comments
 (0)