Skip to content

Commit

Permalink
refactor: adds certLifetime to replace before/after timestamps.
Browse files Browse the repository at this point in the history
Signed-off-by: ianhundere <[email protected]>
  • Loading branch information
ianhundere committed Jan 23, 2025
1 parent dc2a6c5 commit b8cd76e
Show file tree
Hide file tree
Showing 9 changed files with 2,574 additions and 1,214 deletions.
10 changes: 7 additions & 3 deletions cmd/certificate_maker/certificate_maker.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,18 @@ func runCreate(_ *cobra.Command, _ []string) error {
}

// Validate template paths
if err := certmaker.ValidateTemplatePath(viper.GetString("root-template")); err != nil {
rootTemplate := viper.GetString("root-template")
leafTemplate := viper.GetString("leaf-template")
intermediateTemplate := viper.GetString("intermediate-template")

if err := certmaker.ValidateTemplatePath(rootTemplate); err != nil {
return fmt.Errorf("root template error: %w", err)
}
if err := certmaker.ValidateTemplatePath(viper.GetString("leaf-template")); err != nil {
if err := certmaker.ValidateTemplatePath(leafTemplate); err != nil {
return fmt.Errorf("leaf template error: %w", err)
}
if viper.GetString("intermediate-key-id") != "" {
if err := certmaker.ValidateTemplatePath(viper.GetString("intermediate-template")); err != nil {
if err := certmaker.ValidateTemplatePath(intermediateTemplate); err != nil {
return fmt.Errorf("intermediate template error: %w", err)
}
}
Expand Down
258 changes: 248 additions & 10 deletions cmd/certificate_maker/certificate_maker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,7 @@ func TestRunCreate(t *testing.T) {
"issuer": {
"commonName": "Test TSA Root CA"
},
"notBefore": "2024-01-01T00:00:00Z",
"notAfter": "2025-01-01T00:00:00Z",
"certLifetime": "8760h",
"keyUsage": ["certSign", "crlSign"],
"basicConstraints": {
"isCA": true,
Expand All @@ -179,8 +178,10 @@ func TestRunCreate(t *testing.T) {
"subject": {
"commonName": "Test TSA"
},
"notBefore": "2024-01-01T00:00:00Z",
"notAfter": "2025-01-01T00:00:00Z",
"issuer": {
"commonName": "Test TSA Root CA"
},
"certLifetime": "8760h",
"keyUsage": ["digitalSignature"],
"extKeyUsage": ["TimeStamping"],
"basicConstraints": {
Expand Down Expand Up @@ -324,7 +325,7 @@ func TestRunCreate(t *testing.T) {
viper.Reset()
cmd := &cobra.Command{}
for i := 0; i < len(tt.args); i += 2 {
flag := tt.args[i][2:] // Remove "--" prefix
flag := tt.args[i][2:]
value := tt.args[i+1]
viper.Set(flag, value)
}
Expand Down Expand Up @@ -352,8 +353,7 @@ func TestCreateCommand(t *testing.T) {
"issuer": {
"commonName": "Test TSA Root CA"
},
"notBefore": "2024-01-01T00:00:00Z",
"notAfter": "2025-01-01T00:00:00Z",
"certLifetime": "8760h",
"keyUsage": ["certSign", "crlSign"],
"basicConstraints": {
"isCA": true,
Expand All @@ -365,8 +365,10 @@ func TestCreateCommand(t *testing.T) {
"subject": {
"commonName": "Test TSA"
},
"notBefore": "2024-01-01T00:00:00Z",
"notAfter": "2025-01-01T00:00:00Z",
"issuer": {
"commonName": "Test TSA Root CA"
},
"certLifetime": "8760h",
"keyUsage": ["digitalSignature"],
"extKeyUsage": ["TimeStamping"],
"basicConstraints": {
Expand Down Expand Up @@ -432,7 +434,7 @@ func TestCreateCommand(t *testing.T) {
viper.Reset()
cmd := &cobra.Command{}
for i := 0; i < len(tt.args); i += 2 {
flag := tt.args[i][2:] // Remove "--" prefix
flag := tt.args[i][2:]
value := tt.args[i+1]
viper.Set(flag, value)
}
Expand Down Expand Up @@ -479,3 +481,239 @@ func TestRootCommand(t *testing.T) {
})
}
}

func TestEnvironmentVariableHandling(t *testing.T) {
tests := []struct {
name string
envVars map[string]string
args []string
wantError bool
errorString string
}{
{
name: "AWS KMS from environment",
envVars: map[string]string{
"KMS_TYPE": "awskms",
"AWS_REGION": "us-west-2",
"KMS_ROOT_KEY_ID": "alias/test-root",
"KMS_LEAF_KEY_ID": "alias/test-leaf",
},
args: []string{"create"},
wantError: true,
},
{
name: "GCP KMS from environment",
envVars: map[string]string{
"KMS_TYPE": "gcpkms",
"GOOGLE_APPLICATION_CREDENTIALS": "/path/to/creds.json",
"KMS_ROOT_KEY_ID": "projects/test/locations/global/keyRings/test/cryptoKeys/root/cryptoKeyVersions/1",
"KMS_LEAF_KEY_ID": "projects/test/locations/global/keyRings/test/cryptoKeys/leaf/cryptoKeyVersions/1",
},
args: []string{"create"},
wantError: true,
errorString: "credentials file not found",
},
{
name: "Azure KMS from environment",
envVars: map[string]string{
"KMS_TYPE": "azurekms",
"AZURE_TENANT_ID": "test-tenant",
"KMS_ROOT_KEY_ID": "azurekms:name=test-key;vault=test-vault",
"KMS_LEAF_KEY_ID": "azurekms:name=test-key;vault=test-vault",
},
args: []string{"create"},
wantError: true,
},
{
name: "HashiVault KMS from environment",
envVars: map[string]string{
"KMS_TYPE": "hashivault",
"VAULT_TOKEN": "test-token",
"VAULT_ADDR": "http://vault:8200",
"KMS_ROOT_KEY_ID": "transit/keys/test-root",
"KMS_LEAF_KEY_ID": "transit/keys/test-leaf",
},
args: []string{"create"},
wantError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
oldEnv := map[string]string{}
for k := range tt.envVars {
if v, ok := os.LookupEnv(k); ok {
oldEnv[k] = v
}
}

for k, v := range tt.envVars {
os.Setenv(k, v)
}

viper.Reset()

viper.BindEnv("kms-type", "KMS_TYPE")
viper.BindEnv("aws-region", "AWS_REGION")
viper.BindEnv("azure-tenant-id", "AZURE_TENANT_ID")
viper.BindEnv("gcp-credentials-file", "GOOGLE_APPLICATION_CREDENTIALS")
viper.BindEnv("vault-token", "VAULT_TOKEN")
viper.BindEnv("vault-address", "VAULT_ADDR")
viper.BindEnv("root-key-id", "KMS_ROOT_KEY_ID")
viper.BindEnv("leaf-key-id", "KMS_LEAF_KEY_ID")

defer func() {
for k := range tt.envVars {
if v, ok := oldEnv[k]; ok {
os.Setenv(k, v)
} else {
os.Unsetenv(k)
}
}
}()

cmd := &cobra.Command{
Use: "test",
RunE: runCreate,
}

cmd.Flags().String("kms-type", "", "KMS type")
cmd.Flags().String("aws-region", "", "AWS region")
cmd.Flags().String("azure-tenant-id", "", "Azure tenant ID")
cmd.Flags().String("gcp-credentials-file", "", "GCP credentials file")
cmd.Flags().String("vault-token", "", "HashiVault token")
cmd.Flags().String("vault-address", "", "HashiVault address")
cmd.Flags().String("root-key-id", "", "Root key ID")
cmd.Flags().String("leaf-key-id", "", "Leaf key ID")
cmd.Flags().String("root-template", "templates/root-template.json", "Root template")
cmd.Flags().String("leaf-template", "templates/leaf-template.json", "Leaf template")

viper.BindPFlag("kms-type", cmd.Flags().Lookup("kms-type"))
viper.BindPFlag("aws-region", cmd.Flags().Lookup("aws-region"))
viper.BindPFlag("azure-tenant-id", cmd.Flags().Lookup("azure-tenant-id"))
viper.BindPFlag("gcp-credentials-file", cmd.Flags().Lookup("gcp-credentials-file"))
viper.BindPFlag("vault-token", cmd.Flags().Lookup("vault-token"))
viper.BindPFlag("vault-address", cmd.Flags().Lookup("vault-address"))
viper.BindPFlag("root-key-id", cmd.Flags().Lookup("root-key-id"))
viper.BindPFlag("leaf-key-id", cmd.Flags().Lookup("leaf-key-id"))
viper.BindPFlag("root-template", cmd.Flags().Lookup("root-template"))
viper.BindPFlag("leaf-template", cmd.Flags().Lookup("leaf-template"))

cmd.SetArgs(tt.args)
err := cmd.Execute()

if tt.wantError {
require.Error(t, err)
if tt.errorString != "" {
assert.Contains(t, err.Error(), tt.errorString)
}
} else {
require.NoError(t, err)
}

assert.Equal(t, tt.envVars["KMS_TYPE"], viper.GetString("kms-type"))
assert.Equal(t, tt.envVars["KMS_ROOT_KEY_ID"], viper.GetString("root-key-id"))
assert.Equal(t, tt.envVars["KMS_LEAF_KEY_ID"], viper.GetString("leaf-key-id"))
})
}
}

func TestKMSProviderConfigurationValidation(t *testing.T) {
tests := []struct {
name string
args []string
wantError bool
errorString string
}{
{
name: "AWS KMS invalid key format",
args: []string{
"create",
"--kms-type", "awskms",
"--aws-region", "us-west-2",
"--root-key-id", "invalid-format",
"--leaf-key-id", "invalid-format",
},
wantError: true,
errorString: "must start with 'arn:aws:kms:' or 'alias/'",
},
{
name: "GCP KMS missing key version",
args: []string{
"create",
"--kms-type", "gcpkms",
"--root-key-id", "projects/test/locations/global/keyRings/test/cryptoKeys/test",
"--leaf-key-id", "projects/test/locations/global/keyRings/test/cryptoKeys/test",
},
wantError: true,
errorString: "must contain '/cryptoKeyVersions/'",
},
{
name: "Azure KMS invalid key format",
args: []string{
"create",
"--kms-type", "azurekms",
"--azure-tenant-id", "test-tenant",
"--root-key-id", "invalid-format",
"--leaf-key-id", "invalid-format",
},
wantError: true,
errorString: "must start with 'azurekms:name='",
},
{
name: "HashiVault KMS invalid key path",
args: []string{
"create",
"--kms-type", "hashivault",
"--vault-token", "test-token",
"--vault-address", "http://vault:8200",
"--root-key-id", "invalid/path",
"--leaf-key-id", "invalid/path",
},
wantError: true,
errorString: "must be in format: transit/keys/keyname",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
viper.Reset()
cmd := &cobra.Command{
Use: "test",
RunE: runCreate,
}

cmd.Flags().String("kms-type", "", "KMS type")
cmd.Flags().String("aws-region", "", "AWS region")
cmd.Flags().String("azure-tenant-id", "", "Azure tenant ID")
cmd.Flags().String("vault-token", "", "HashiVault token")
cmd.Flags().String("vault-address", "", "HashiVault address")
cmd.Flags().String("root-key-id", "", "Root key ID")
cmd.Flags().String("leaf-key-id", "", "Leaf key ID")
cmd.Flags().String("root-template", "templates/root-template.json", "Root template")
cmd.Flags().String("leaf-template", "templates/leaf-template.json", "Leaf template")

viper.BindPFlag("kms-type", cmd.Flags().Lookup("kms-type"))
viper.BindPFlag("aws-region", cmd.Flags().Lookup("aws-region"))
viper.BindPFlag("azure-tenant-id", cmd.Flags().Lookup("azure-tenant-id"))
viper.BindPFlag("vault-token", cmd.Flags().Lookup("vault-token"))
viper.BindPFlag("vault-address", cmd.Flags().Lookup("vault-address"))
viper.BindPFlag("root-key-id", cmd.Flags().Lookup("root-key-id"))
viper.BindPFlag("leaf-key-id", cmd.Flags().Lookup("leaf-key-id"))
viper.BindPFlag("root-template", cmd.Flags().Lookup("root-template"))
viper.BindPFlag("leaf-template", cmd.Flags().Lookup("leaf-template"))

cmd.SetArgs(tt.args)
err := cmd.Execute()

if tt.wantError {
require.Error(t, err)
if tt.errorString != "" {
assert.Contains(t, err.Error(), tt.errorString)
}
} else {
require.NoError(t, err)
}
})
}
}
10 changes: 7 additions & 3 deletions pkg/certmaker/certmaker.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,10 +432,14 @@ func ValidateKMSConfig(config KMSConfig) error {
if keyID == "" {
return nil
}
if strings.Contains(keyID, "/") {
return fmt.Errorf("hashivault %s must be a simple key name without path separators", keyType)
parts := strings.Split(keyID, "/")
if len(parts) < 3 {
return fmt.Errorf("hashivault %s must be in format: transit/keys/keyname", keyType)
}
if strings.TrimSpace(keyID) == "" {
if parts[0] != "transit" || parts[1] != "keys" {
return fmt.Errorf("hashivault %s must start with 'transit/keys/'", keyType)
}
if parts[2] == "" {
return fmt.Errorf("key name cannot be empty for %s", keyType)
}
return nil
Expand Down
Loading

0 comments on commit b8cd76e

Please sign in to comment.