Skip to content

Commit

Permalink
feat(metadata): Signer recognizes annotation prefix for Command metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
m8rmclaren committed Dec 5, 2023
1 parent 69bdc7b commit b58d286
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 29 deletions.
21 changes: 1 addition & 20 deletions deploy/charts/k8s-csr-signer/templates/clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,6 @@ metadata:
labels:
{{- include "command-csr-signer.labels" . | nindent 4 }}
rules:
# The controller needs to be able to read secrets and configmaps to get authentication information
# and to configure itself
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch

# configuration validation webhook controller
- apiGroups: ["admissionregistration.k8s.io"]
resources: ["validatingwebhookconfigurations"]
Expand All @@ -44,4 +25,4 @@ rules:
resourceNames:
{{- toYaml . | nindent 6 }}
{{- end }}
verbs: ["approve", "sign"]
verbs: ["sign"]
38 changes: 38 additions & 0 deletions deploy/charts/k8s-csr-signer/templates/secretrole.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
{{- include "command-csr-signer.labels" . | nindent 4 }}
name: {{ include "command-csr-signer.name" . }}-secret-reader-role
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
{{- include "command-csr-signer.labels" . | nindent 4 }}
name: {{ include "command-csr-signer.name" . }}-secret-reader-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "command-csr-signer.name" . }}-secret-reader-role
subjects:
- kind: ServiceAccount
name: {{ include "command-csr-signer.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
9 changes: 9 additions & 0 deletions docs/annotations.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ Here are the supported annotations that can override the default values:
k8s-csr-signer.keyfactor.com/chainDepth: 3
```

### Metadata Annotations

The Keyfactor Command K8s CSR Signer also allows you to specify Command Metadata through the use of annotations. Metadata attached to a certificate request will be stored in Command and can be used for reporting and auditing purposes. The syntax for specifying metadata is as follows:
```yaml
metadata.k8s-csr-signer.keyfactor.com/<metadata-field-name>: <metadata-value>
```

Ensure that the metadata specified by the `metadata-field-name` matches a name of a metadata field in Command exactly. If the metadata field name does not match, the CSR enrollment will fail.

### How to Apply Annotations

To apply these annotations, include them in the metadata section of your CertificateSigningRequest resource:
Expand Down
55 changes: 55 additions & 0 deletions docs/getting-started.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,61 @@
* Helm (to deploy to Kubernetes)
* [Helm](https://helm.sh/docs/intro/install/) (v3.1 +)

### Keyfactor Command Configuration
The Command K8s CSR Signer populates metadata fields on issued certificates in Command pertaining to the K8s cluster and CertificateSigningRequest reconcile loops. Before deploying any CSRs in the cluster, these metadata fields must be created in Command. To easily create these metadata fields, use the `kfutil` Keyfactor command line tool that offers convenient and powerful command line access to the Keyfactor platform. Before proceeding, ensure that `kfutil` is installed and configured by following the instructions here: [https://github.com/Keyfactor/kfutil](https://github.com/Keyfactor/kfutil).

Then, use the `import` command to import the metadata fields into Command:
```shell
cat <<EOF >> metadata.json
{
"Collections": [],
"MetadataFields": [
{
"AllowAPI": true,
"DataType": 1,
"Description": "The reconcile ID of the reconcile loop that signed the certificate.",
"Name": "Controller-Reconcile-Id"
},
{
"AllowAPI": true,
"DataType": 1,
"Description": "The namespace that the controller reconciler is running in.",
"Name": "Controller-Namespace"
},
{
"AllowAPI": true,
"DataType": 1,
"Description": "The name of the resource that a K8s controller is reconciling.",
"Name": "Controller-Kind"
},
{
"AllowAPI": true,
"DataType": 1,
"Description": "The group name of the resource that a K8s controller is reconciling.",
"ExplicitUpdate": false,
"Name": "Controller-Resource-Group-Name"
},
{
"AllowAPI": true,
"DataType": 1,
"Description": "The name of the resource being reconciled in Kubernetes",
"Name": "Controller-Resource-Name",
}
],
"ExpirationAlerts": [],
"IssuedCertAlerts": [],
"DeniedCertAlerts": [],
"PendingCertAlerts": [],
"Networks": [],
"WorkflowDefinitions": [],
"BuiltInReports": [],
"CustomReports": [],
"SecurityRoles": []
}
EOF
kfutil import --metadata --file metadata.json
```

## Getting Started
Install required software and their dependencies if not already present. Additionally, verify that at least one Kubernetes node is running by running the following command:

Expand Down
13 changes: 12 additions & 1 deletion internal/controllers/certificatesigningrequest_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"k8s.io/utils/clock"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
)

type CertificateSigningRequestReconciler struct {
Expand All @@ -45,6 +46,8 @@ type CertificateSigningRequestReconciler struct {
func (c *CertificateSigningRequestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) {
reconcileLog := ctrl.LoggerFrom(ctx)

meta := signer.K8sMetadata{}

c.SignerBuilder.Reset()

// Get the CertificateSigningRequest
Expand Down Expand Up @@ -116,12 +119,20 @@ func (c *CertificateSigningRequestReconciler) Reconcile(ctx context.Context, req
}
}

// Populate metadata
meta.ControllerNamespace = c.ClusterResourceNamespace
meta.ControllerKind = "CertificateSigningRequest"
meta.ControllerResourceGroupName = "certificatesigningrequests.certificates.k8s.io"
meta.ControllerReconcileId = string(controller.ReconcileIDFromContext(ctx))
meta.ControllerResourceName = certificateSigningRequest.GetName()

// Apply the configuration to the signer builder
c.SignerBuilder.
WithContext(ctx).
WithCredsSecret(creds).
WithConfigMap(config).
WithCACertConfigMap(root)
WithCACertConfigMap(root).
WithMetadata(meta)

// Validate that there were no issues with the configuration
err = c.SignerBuilder.PreFlight()
Expand Down
4 changes: 4 additions & 0 deletions internal/controllers/fake_signer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ func (f *FakeSignerBuilder) WithCACertConfigMap(configMap corev1.ConfigMap) sign
return f
}

func (f *FakeSignerBuilder) WithMetadata(meta signer.K8sMetadata) signer.Builder {
return f
}

func (f *FakeSignerBuilder) PreFlight() error {
return nil
}
Expand Down
49 changes: 47 additions & 2 deletions internal/signer/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ var _ Builder = &commandSigner{}
var _ Signer = &commandSigner{}

const (
enrollmentPEMFormat = "PEM"
annotationPrefix = "k8s-csr-signer.keyfactor.com/"
enrollmentPEMFormat = "PEM"
annotationPrefix = "k8s-csr-signer.keyfactor.com/"
commandMetadataAnnotationPrefix = "metadata.k8s-csr-signer.keyfactor.com/"
)

type Builder interface {
Expand All @@ -33,6 +34,7 @@ type Builder interface {
WithCredsSecret(corev1.Secret) Builder
WithConfigMap(corev1.ConfigMap) Builder
WithCACertConfigMap(corev1.ConfigMap) Builder
WithMetadata(meta K8sMetadata) Builder
PreFlight() error
Build() Signer
}
Expand All @@ -41,11 +43,30 @@ type Signer interface {
Sign(csr certificates.CertificateSigningRequest) ([]byte, error)
}

type K8sMetadata struct {
ControllerNamespace string
ControllerKind string
ControllerResourceGroupName string
ControllerReconcileId string
ControllerResourceName string
}

const (
CommandMetaControllerNamespace = "Controller-Namespace"
CommandMetaControllerKind = "Controller-Kind"
CommandMetaControllerResourceGroupName = "Controller-Resource-Group-Name"
CommandMetaControllerReconcileId = "Controller-Reconcile-Id"
CommandMetaControllerResourceName = "Controller-Resource-Name"
)

type commandSigner struct {
ctx context.Context
logger logr.Logger
creds corev1.Secret

// Meta
meta K8sMetadata

// Given from config
hostname string
defaultCertificateTemplate string
Expand Down Expand Up @@ -162,6 +183,11 @@ func (s *commandSigner) WithCACertConfigMap(config corev1.ConfigMap) Builder {
return s
}

func (s *commandSigner) WithMetadata(meta K8sMetadata) Builder {
s.meta = meta
return s
}

func (s *commandSigner) PreFlight() error {
var err error

Expand Down Expand Up @@ -271,6 +297,25 @@ func (s *commandSigner) Sign(csr certificates.CertificateSigningRequest) ([]byte
}
}

// Construct metadata map
meta := map[string]interface{}{
CommandMetaControllerNamespace: s.meta.ControllerNamespace,
CommandMetaControllerKind: s.meta.ControllerKind,
CommandMetaControllerResourceGroupName: s.meta.ControllerResourceGroupName,
CommandMetaControllerReconcileId: s.meta.ControllerReconcileId,
CommandMetaControllerResourceName: s.meta.ControllerResourceName,
}

// Set custom metadata from annotations
for key, value := range annotations {
if strings.HasPrefix(key, commandMetadataAnnotationPrefix) {
meta[strings.TrimPrefix(key, commandMetadataAnnotationPrefix)] = value
}
}

// Set metadata on enrollment request
enroll.SetMetadata(meta)

// Construct CA name from hostname and logical name
var caBuilder strings.Builder
if certificateAuthorityHostname != "" {
Expand Down
39 changes: 33 additions & 6 deletions internal/signer/signer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,30 @@ func TestCommandSignerBuilder(t *testing.T) {
}
})
})

t.Run("WithMetadata", func(t *testing.T) {
meta := GetFakeMetadata()

signer.WithContext(ctrl.LoggerInto(context.TODO(), logrtesting.New(t)))

signer.WithMetadata(meta)

assert.Equal(t, meta.ControllerNamespace, signer.meta.ControllerNamespace)
assert.Equal(t, meta.ControllerKind, signer.meta.ControllerKind)
assert.Equal(t, meta.ControllerResourceGroupName, signer.meta.ControllerResourceGroupName)
assert.Equal(t, meta.ControllerReconcileId, signer.meta.ControllerReconcileId)
assert.Equal(t, meta.ControllerResourceName, signer.meta.ControllerResourceName)
})
}

func GetFakeMetadata() K8sMetadata {
return K8sMetadata{
ControllerNamespace: "fake-namespace",
ControllerKind: "fake-kind",
ControllerResourceGroupName: "fake-resource-group",
ControllerReconcileId: "fake-reconcile-id",
ControllerResourceName: "fake-resource-name",
}
}

func TestCommandSigner(t *testing.T) {
Expand Down Expand Up @@ -238,7 +262,8 @@ func TestCommandSigner(t *testing.T) {
WithContext(ctrl.LoggerInto(context.TODO(), logrtesting.New(t))).
WithCredsSecret(creds).
WithConfigMap(signerConfig).
WithCACertConfigMap(caConfig)
WithCACertConfigMap(caConfig).
WithMetadata(GetFakeMetadata())

err = builder.PreFlight()
if err != nil {
Expand Down Expand Up @@ -281,10 +306,11 @@ func TestCommandSigner(t *testing.T) {

// Create supported annotations
supportedAnnotations := map[string]string{
"k8s-csr-signer.keyfactor.com/certificateTemplate": commandConfig.commandCertificateTemplate,
"k8s-csr-signer.keyfactor.com/certificateAuthorityHostname": commandConfig.commandCertificateAuthorityHostname,
"k8s-csr-signer.keyfactor.com/certificateAuthorityLogicalName": commandConfig.commandCertificateAuthorityLogicalName,
"k8s-csr-signer.keyfactor.com/chainDepth": "5",
annotationPrefix + "certificateTemplate": commandConfig.commandCertificateTemplate,
annotationPrefix + "certificateAuthorityHostname": commandConfig.commandCertificateAuthorityHostname,
annotationPrefix + "certificateAuthorityLogicalName": commandConfig.commandCertificateAuthorityLogicalName,
annotationPrefix + "chainDepth": "5",
commandMetadataAnnotationPrefix + "Email-Contact": "k8s-csr-signer@keyfactor.com",
}

t.Run("BasicAuthWithAnnotations", func(t *testing.T) {
Expand All @@ -309,7 +335,8 @@ func TestCommandSigner(t *testing.T) {
WithContext(ctrl.LoggerInto(context.TODO(), logrtesting.New(t))).
WithCredsSecret(estCreds).
WithConfigMap(signerConfig).
WithCACertConfigMap(caConfig)
WithCACertConfigMap(caConfig).
WithMetadata(GetFakeMetadata())

err = builder.PreFlight()
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions sample/sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ metadata:
k8s-csr-signer.keyfactor.com/certificateAuthorityLogicalName: CommandCA1

k8s-csr-signer.keyfactor.com/chainDepth: "0"
metadata.k8s-csr-signer.keyfactor.com/Email-Contact: "demo-command-signer@command.com"
spec:
request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ25EQ0NBWVFDQVFBd0lqRWdNQjRHQTFVRUF4TVhhemh6TFdOemNpMXphV2R1WlhJdGRHVnpkQzVqYjIwdwpnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFESmcydDI0MjBERzVCZStsM3FSRGE4Cm1rOFdJRDVzaXJJRVh1aEQzMWQ5R3ZqSVJ4bjRqT01SL2Y0ck93MmM2aWNDQVFkUmRNbG5pSFYzM20vb09FekIKTkVLQWpoSWpTTUVERXpsa1d2MGVGcTFnK25RN2c3bUZpVHB0RDhhck9LZlhBM2t3eDU3bURhWkJHTDNPaXlqUAo5Y2Y5NWl2VldDa2JNZk9JV0ZlTVIyRnY3Um1PcEFiaW1JUnBnSWlnd3dROHVzaVRhN3FNcUJBa2R0b2w2MjJKCkxaWUloSGs2WVB1b253MnBoRHNZL1pkY2dLSi9CaGlaTC9ibng3NlJ0WnRWRVF6OSsySmdWTjA2eHJNUVIyUnoKVUdMbGRKb2dIWDQwTUhQYWRMNXdSZkJiaTVQa0VxeTF4RzRwYUFUY29lNVk2YTlRREQ2cFZXeDhjanVpTXJKOQpBZ01CQUFHZ05UQXpCZ2txaGtpRzl3MEJDUTR4SmpBa01DSUdBMVVkRVFRYk1CbUNGMnM0Y3kxamMzSXRjMmxuCmJtVnlMWFJsYzNRdVkyOXRNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJWUnV0dGk3V0lpY3VMdGxFT2UzZzYKYjdQUzI0Q1hadEhxQ2tDVENZWmtkR1NNaXRPcUljajduenFnWTlndWdjYURSVERWcExkTVo4SWtoZGhuR3BZRgo2R25ma0poQmRQNVg0U3JsdGlRUml0YjNQZXJQTm8rU29xbFFXNTl2S3JselRHVng4MldNbjNYc2JLY0VOam9vCktXUjJKTHVvVVNlOGJqdit1T0E4SVlqQW9WZEtGMkNJVCtIZmVZNzVOcnQ1RVlqZ2tzL0JTVmsva05lRXJKM3kKSEthN3JzeUVFQXpxcmY1QmNXM1B5UlU3UitIWmkxZlpudnlzRTA5Nnd3QlVBb01OYmZ5OFFRUlpXV2l1NTJpMwphSko4VUR4SlcyTys0UGF1Q3haVm0yeUQ1UjdzekYrZjMyeXFjT0xOYVNDWndmNmNrYUNEZmphTjRlUzJnM1drCi0tLS0tRU5EIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLQo=
usages:
Expand Down

0 comments on commit b58d286

Please sign in to comment.