AB#2504: Deploy join-service via helm (#358)

This commit is contained in:
Otto Bittner 2022-10-24 12:23:18 +02:00 committed by GitHub
parent d46408d00b
commit c2814aeddb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 434 additions and 378 deletions

View file

@ -7,3 +7,5 @@ version: 2.2.0-pre
dependencies:
- name: kms
version: 2.2.0-pre
- name: join-service
version: 2.2.0-pre

View file

@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View file

@ -0,0 +1,5 @@
apiVersion: v2
name: join-service
description: A chart to deploy the Constellation join-service
type: application
version: 2.2.0-pre

View file

@ -0,0 +1,24 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: join-service
name: join-service
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- create
- update
- apiGroups:
- rbac.authorization.k8s.io
resources:
- roles
- rolebindings
verbs:
- create
- update

View file

@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: join-service
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: join-service
subjects:
- kind: ServiceAccount
name: join-service
namespace: {{ .Values.namespace }}

View file

@ -0,0 +1,15 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: join-config
namespace: kube-system
data:
# mustToJson is required so the json-strings passed from go are properly quoted in the rendered yaml.
enforcedPCRs: {{ .Values.enforcedPCRs | mustToJson }}
measurements: {{ .Values.measurements | mustToJson }}
{{- if eq .Values.csp "azure" }}
enforceIdKeyDigest: {{ .Values.enforceIdKeyDigest }}
idkeydigest: {{ .Values.idkeydigest }}
{{- end }}
binaryData:
measurementSalt: {{ .Values.measurementSalt }}

View file

@ -0,0 +1,69 @@
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: join-service
namespace: {{ .Values.namespace }}
labels:
component: join-service
k8s-app: join-service
kubernetes.io/cluster-service: "true"
spec:
selector:
matchLabels:
k8s-app: join-service
template:
metadata:
labels:
k8s-app: join-service
spec:
priorityClassName: system-cluster-critical
serviceAccountName: join-service
tolerations:
- key: CriticalAddonsOnly
operator: Exists
- effect: NoSchedule
key: node-role.kubernetes.io/master
operator: Equal
value: "true"
- effect: NoSchedule
key: node-role.kubernetes.io/control-plane
operator: Exists
- effect: NoExecute
operator: Exists
- effect: NoSchedule
operator: Exists
nodeSelector:
node-role.kubernetes.io/control-plane: ""
containers:
- name: join-service
image: {{ .Values.image }}
args:
- --cloud-provider={{ .Values.csp }}
- --kms-endpoint=kms.kube-system:{{ .Values.global.kmsPort }}
volumeMounts:
- mountPath: {{ .Values.global.serviceBasePath }}
name: config
readOnly: true
- mountPath: /etc/kubernetes
name: kubeadm
readOnly: true
ports:
- containerPort: {{ .Values.joinServicePort }}
name: tcp
resources: {}
securityContext:
privileged: true
volumes:
- name: config
projected:
sources:
- configMap:
name: {{ .Values.global.joinConfigCMName }}
- configMap:
name: {{ .Values.global.k8sVersionCMName }}
- configMap:
name: {{ .Values.global.internalCMName }}
- name: kubeadm
hostPath:
path: /etc/kubernetes
updateStrategy: {}

View file

@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
name: join-service
namespace: {{ .Values.namespace }}
spec:
type: NodePort
selector:
k8s-app: join-service
ports:
- name: grpc
protocol: TCP
port: {{ .Values.joinServicePort }}
targetPort: {{ .Values.joinServicePort }}
nodePort: {{ .Values.joinServiceNodePort }}
status:
loadBalancer: {}

View file

@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: join-service
namespace: {{ .Values.namespace }}

View file

@ -0,0 +1,53 @@
{
"$schema": "https://json-schema.org/draft-07/schema#",
"properties": {
"csp": {
"description": "CSP to which the chart is deployed.",
"enum": ["Azure", "GCP", "AWS", "QEMU"]
},
"enforcedPCRs": {
"description": "JSON-string to describe the enforced PCRs.",
"type": "string",
"examples": ["[1, 15]"]
},
"measurements": {
"description": "JSON-string to describe the expected measurements.",
"type": "string",
"examples": ["{'1':'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA','15':'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='}"]
},
"enforceIdKeyDigest": {
"description": "Whether or not idkeydigest should be enforced during attestation on azure.",
"type": "boolean"
},
"idkeydigest": {
"description": "Expected idkeydigest value for Azure SNP attestation.",
"type": "string",
"examples": ["57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696"]
},
"image": {
"description": "Container image to use for the spawned pods.",
"type": "string",
"examples": ["ghcr.io/edgelesssys/constellation/join-service:latest"],
"pattern": "ghcr.io/edgelesssys/constellation/join-service:.+"
},
"measurementSalt": {
"description": "Salt used to generate node measurements",
"type": "string",
"examples": ["AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"]
}
},
"required": [
"csp",
"enforcedPCRs",
"measurements",
"measurementSalt",
"image"
],
"if": {
"properties": { "csp": { "const": "azure" } },
"required": ["csp"]
},
"then": { "required": ["enforceIdKeyDigest", "idkeydigest"] },
"title": "Values",
"type": "object"
}

View file

@ -0,0 +1,5 @@
# Namespace to which to deploy
namespace: "kube-system"
csp: "gcp"
joinServicePort: 9090
joinServiceNodePort: 30090

View file

@ -17,15 +17,15 @@ spec:
k8s-app: kms
spec:
containers:
- args:
- --port={{ .Values.port }}
image: {{ .Values.image }}
name: kms
resources: {}
volumeMounts:
- mountPath: {{ .Values.serviceBasePath }}
name: config
readOnly: true
- name: kms
image: {{ .Values.image }}
args:
- --port={{ .Values.global.kmsPort }}
volumeMounts:
- mountPath: {{ .Values.global.serviceBasePath }}
name: config
readOnly: true
resources: {}
nodeSelector:
node-role.kubernetes.io/control-plane: ""
priorityClassName: system-cluster-critical
@ -52,7 +52,7 @@ spec:
items:
- key: {{ .Values.measurementsFilename }}
path: {{ .Values.measurementsFilename }}
name: {{ .Values.joinConfigCMName }}
name: {{ .Values.global.joinConfigCMName }}
- secret:
items:
- key: {{ .Values.masterSecretKeyName }}

View file

@ -6,9 +6,9 @@ metadata:
spec:
ports:
- name: grpc
port: {{ .Values.port }}
port: {{ .Values.global.kmsPort }}
protocol: TCP
targetPort: {{ .Values.port }}
targetPort: {{ .Values.global.kmsPort }}
selector:
k8s-app: kms
type: ClusterIP

View file

@ -1,11 +1,5 @@
# Namespace to which KMS will be deployed.
namespace: "kube-system"
# Port on which the service will listen.
port: 9000
# Name of the ConfigMap that holds measurements and other info.
joinConfigCMName: join-config
# Path to which secrets/CMs are mounted.
serviceBasePath: /var/config
# Name of the key within the respective secret that holds the salt.
saltKeyName: salt
# Name of the secret that contains the master secret.

View file

@ -0,0 +1,11 @@
global:
# Port on which the KMS service will listen. Global since join-service also uses the value.
kmsPort: 9000
# Path to which secrets/CMs are mounted.
serviceBasePath: /var/config
# Name of the ConfigMap that holds measurements and other info.
joinConfigCMName: join-config
# Name of the ConfigMap that holds the installed k8s version.
k8sVersionCMName: k8s-version
# Name of the ConfigMap that holds configs that should not be modified by the user.
internalCMName: internal-config

View file

@ -36,13 +36,13 @@ var HelmFS embed.FS
type ChartLoader struct{}
func (i *ChartLoader) Load(csp cloudprovider.Provider, conformanceMode bool, masterSecret []byte, salt []byte) ([]byte, error) {
func (i *ChartLoader) Load(csp cloudprovider.Provider, conformanceMode bool, masterSecret []byte, salt []byte, enforcedPCRs []uint32, enforceIDKeyDigest bool) ([]byte, error) {
ciliumRelease, err := i.loadCilium(csp, conformanceMode)
if err != nil {
return nil, err
}
conServicesRelease, err := i.loadConstellationServices(masterSecret, salt)
conServicesRelease, err := i.loadConstellationServices(csp, masterSecret, salt, enforcedPCRs, enforceIDKeyDigest)
if err != nil {
return nil, err
}
@ -90,7 +90,8 @@ func (i *ChartLoader) loadCilium(csp cloudprovider.Provider, conformanceMode boo
return helm.Release{Chart: chartRaw, Values: ciliumVals, ReleaseName: "cilium", Wait: true}, nil
}
func (i *ChartLoader) loadConstellationServices(masterSecret []byte, salt []byte) (helm.Release, error) {
// loadConstellationServices loads the constellation-services chart from the embed.FS, marshals it into a helm-package .tgz and sets the values that can be set in the CLI.
func (i *ChartLoader) loadConstellationServices(csp cloudprovider.Provider, masterSecret []byte, salt []byte, enforcedPCRs []uint32, enforceIDKeyDigest bool) (helm.Release, error) {
chart, err := loadChartsDir(HelmFS, "charts/edgeless/constellation-services")
if err != nil {
return helm.Release{}, fmt.Errorf("loading constellation-services chart: %w", err)
@ -101,20 +102,40 @@ func (i *ChartLoader) loadConstellationServices(masterSecret []byte, salt []byte
return helm.Release{}, fmt.Errorf("packaging chart: %w", err)
}
enforcedPCRsJSON, err := json.Marshal(enforcedPCRs)
if err != nil {
return helm.Release{}, fmt.Errorf("marshaling enforcedPCRs: %w", err)
}
vals := map[string]interface{}{
"global": map[string]interface{}{
"kmsPort": constants.KMSPort,
"serviceBasePath": constants.ServiceBasePath,
"joinConfigCMName": constants.JoinConfigMap,
"k8sVersionCMName": constants.K8sVersion,
"internalCMName": constants.InternalConfigMap,
},
"kms": map[string]interface{}{
"namespace": constants.ConstellationNamespace,
"port": constants.KMSPort,
"joinConfigCMName": constants.JoinConfigMap,
"serviceBasePath": constants.ServiceBasePath,
"image": versions.KmsImage,
"masterSecretName": constants.ConstellationMasterSecretStoreName,
"masterSecretKeyName": constants.ConstellationMasterSecretKey,
"saltKeyName": constants.ConstellationSaltKey,
"measurementsFilename": constants.MeasurementsFilename,
"masterSecret": base64.StdEncoding.EncodeToString(masterSecret),
"salt": base64.StdEncoding.EncodeToString(salt),
"namespace": constants.ConstellationNamespace,
"saltKeyName": constants.ConstellationSaltKey,
"masterSecretKeyName": constants.ConstellationMasterSecretKey,
"masterSecretName": constants.ConstellationMasterSecretStoreName,
"measurementsFilename": constants.MeasurementsFilename,
},
"join-service": map[string]interface{}{
"csp": csp,
"enforcedPCRs": string(enforcedPCRsJSON),
"image": versions.JoinImage,
"namespace": constants.ConstellationNamespace,
},
}
if csp == cloudprovider.Azure {
joinServiceVals := vals["join-service"].(map[string]interface{})
joinServiceVals["enforceIDKeyDigest"] = enforceIDKeyDigest
}
return helm.Release{Chart: chartRaw, Values: vals, ReleaseName: "constellation-services", Wait: true}, nil

View file

@ -21,7 +21,7 @@ func TestLoad(t *testing.T) {
assert := assert.New(t)
chartLoader := ChartLoader{}
release, err := chartLoader.Load(cloudprovider.GCP, true, []byte("secret"), []byte("salt"))
release, err := chartLoader.Load(cloudprovider.GCP, true, []byte("secret"), []byte("salt"), nil, false)
assert.NoError(err)
var helmReleases helm.Releases