├── templates ├── .gitkeep └── hooks │ └── certificate │ ├── delta_pre_compare.go.tpl │ ├── sdk_update_post_set_output.go.tpl │ ├── sdk_create_pre_build_request.go.tpl │ ├── late_initialize_post_read_one.go.tpl │ ├── sdk_create_post_build_request.go.tpl │ ├── sdk_read_one_pre_set_output.go.tpl │ ├── sdk_file_end.go.tpl │ └── sdk_update_pre_build_request.go.tpl ├── test └── e2e │ ├── tests │ ├── .gitkeep │ └── __init__.py │ ├── resources │ ├── .gitkeep │ ├── certificate_public.yaml │ ├── certificate_imported.yaml │ └── certificate_public_invalid.yaml │ ├── .gitignore │ ├── requirements.txt │ ├── replacement_values.py │ ├── service_cleanup.py │ ├── bootstrap_resources.py │ ├── __init__.py │ ├── service_bootstrap.py │ ├── conftest.py │ ├── x509.py │ ├── certificate.py │ └── condition.py ├── NOTICE ├── OWNERS ├── .gitignore ├── config ├── rbac │ ├── service-account.yaml │ ├── kustomization.yaml │ ├── role-reader.yaml │ ├── cluster-role-binding.yaml │ ├── leader-election-role-binding.yaml │ ├── leader-election-role.yaml │ ├── role-writer.yaml │ └── cluster-role-controller.yaml ├── overlays │ └── namespaced │ │ ├── role.json │ │ ├── role-binding.json │ │ └── kustomization.yaml ├── crd │ ├── kustomization.yaml │ └── common │ │ ├── kustomization.yaml │ │ └── bases │ │ ├── services.k8s.aws_iamroleselectors.yaml │ │ └── services.k8s.aws_fieldexports.yaml ├── controller │ ├── kustomization.yaml │ ├── service.yaml │ └── deployment.yaml ├── default │ └── kustomization.yaml └── iam │ └── recommended-inline-policy ├── apis └── v1alpha1 │ ├── doc.go │ ├── ack-generate-metadata.yaml │ ├── groupversion_info.go │ ├── generator.yaml │ ├── enums.go │ └── types.go ├── .github └── workflows │ ├── postsubmit.yaml │ └── create-release.yml ├── metadata.yaml ├── SECURITY.md ├── CODE_OF_CONDUCT.md ├── OWNERS_ALIASES ├── Makefile ├── helm ├── Chart.yaml ├── templates │ ├── role-reader.yaml │ ├── service-account.yaml │ ├── NOTES.txt │ ├── role-writer.yaml │ ├── leader-election-role.yaml │ ├── leader-election-role-binding.yaml │ ├── metrics-service.yaml │ ├── caches-role.yaml │ ├── cluster-role-controller.yaml │ ├── caches-role-binding.yaml │ ├── cluster-role-binding.yaml │ ├── _helpers.tpl │ └── deployment.yaml ├── crds │ ├── services.k8s.aws_iamroleselectors.yaml │ └── services.k8s.aws_fieldexports.yaml ├── values.yaml └── values.schema.json ├── documentation.yaml ├── pkg ├── version │ └── version.go ├── resource │ ├── certificate │ │ ├── identifiers.go │ │ ├── manager_factory.go │ │ ├── tags.go │ │ ├── resource.go │ │ ├── delta.go │ │ ├── descriptor.go │ │ ├── references.go │ │ └── hooks.go │ └── registry.go └── tags │ └── sync.go ├── olm └── olmconfig.yaml ├── GOVERNANCE.md ├── CONTRIBUTING.md ├── README.md ├── go.mod ├── cmd └── controller │ └── main.go └── generator.yaml /templates/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/e2e/tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/e2e/resources/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /templates/hooks/certificate/delta_pre_compare.go.tpl: -------------------------------------------------------------------------------- 1 | compareCertificateIssuedAt(delta, a, b) -------------------------------------------------------------------------------- /test/e2e/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | **/bootstrap.yaml 4 | **/bootstrap.pkl 5 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners 2 | 3 | approvers: 4 | - core-ack-team -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | *~ 4 | .idea 5 | bin 6 | build 7 | .env 8 | READ_BEFORE_COMMIT.md 9 | -------------------------------------------------------------------------------- /config/rbac/service-account.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: ack-acm-controller 6 | namespace: ack-system 7 | -------------------------------------------------------------------------------- /config/overlays/namespaced/role.json: -------------------------------------------------------------------------------- 1 | [{"op": "replace", "path": "/kind", "value": "Role"}, 2 | {"op": "add", "path": "/metadata/namespace", "value": "ack-system"}] -------------------------------------------------------------------------------- /test/e2e/requirements.txt: -------------------------------------------------------------------------------- 1 | acktest @ git+https://github.com/aws-controllers-k8s/test-infra.git@5a09bbdb961ea14a65b15b63769134125023ac61 2 | 3 | cryptography == 42.0.8 4 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - common 5 | - bases/acm.services.k8s.aws_certificates.yaml 6 | -------------------------------------------------------------------------------- /apis/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // +k8s:deepcopy-gen=package 2 | // Package v1alpha1 is the v1alpha1 version of the acm.services.k8s.aws API. 3 | // +groupName=acm.services.k8s.aws 4 | package v1alpha1 5 | -------------------------------------------------------------------------------- /templates/hooks/certificate/sdk_update_post_set_output.go.tpl: -------------------------------------------------------------------------------- 1 | ko.Status.IssuedAt = latest.ko.Status.IssuedAt 2 | ko.Status.Status = latest.ko.Status.Status 3 | ko.Status.Serial = latest.ko.Status.Serial -------------------------------------------------------------------------------- /config/overlays/namespaced/role-binding.json: -------------------------------------------------------------------------------- 1 | [{"op": "replace", "path": "/kind", "value": "RoleBinding"}, 2 | {"op": "add", "path": "/metadata/namespace", "value": "ack-system"}, 3 | {"op": "replace", "path": "/roleRef/kind", "value": "Role"}] -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - cluster-role-binding.yaml 3 | - cluster-role-controller.yaml 4 | - role-reader.yaml 5 | - role-writer.yaml 6 | - service-account.yaml 7 | - leader-election-role.yaml 8 | - leader-election-role-binding.yaml 9 | -------------------------------------------------------------------------------- /.github/workflows/postsubmit.yaml: -------------------------------------------------------------------------------- 1 | name: Hydrate Go Proxy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | call-hydrate-go-proxy: 10 | uses: aws-controllers-k8s/.github/.github/workflows/reusable-postsubmit.yaml@main 11 | -------------------------------------------------------------------------------- /config/crd/common/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Code generated in runtime. DO NOT EDIT. 2 | 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - bases/services.k8s.aws_iamroleselectors.yaml 7 | - bases/services.k8s.aws_fieldexports.yaml 8 | -------------------------------------------------------------------------------- /metadata.yaml: -------------------------------------------------------------------------------- 1 | service: 2 | full_name: "AWS Certificate Manager" 3 | short_name: "ACM" 4 | link: "https://aws.amazon.com/certificate-manager/" 5 | documentation: "https://docs.aws.amazon.com/acm/" 6 | api_versions: 7 | - api_version: v1alpha1 8 | status: available 9 | -------------------------------------------------------------------------------- /config/controller/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - deployment.yaml 3 | - service.yaml 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | images: 7 | - name: controller 8 | newName: public.ecr.aws/aws-controllers-k8s/acm-controller 9 | newTag: 1.3.0 10 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security issue notifications 2 | 3 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 4 | -------------------------------------------------------------------------------- /.github/workflows/create-release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | permissions: 9 | contents: write # For creating releases 10 | 11 | jobs: 12 | call-create-release: 13 | uses: aws-controllers-k8s/.github/.github/workflows/reusable-create-release.yaml@main 14 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /config/rbac/role-reader.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | creationTimestamp: null 6 | name: ack-acm-reader 7 | namespace: default 8 | rules: 9 | - apiGroups: 10 | - acm.services.k8s.aws 11 | resources: 12 | - certificates 13 | verbs: 14 | - get 15 | - list 16 | - watch 17 | -------------------------------------------------------------------------------- /config/controller/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: ack-acm-metrics-service 5 | namespace: ack-system 6 | spec: 7 | selector: 8 | app.kubernetes.io/name: ack-acm-controller 9 | ports: 10 | - name: metricsport 11 | port: 8080 12 | targetPort: http 13 | protocol: TCP 14 | type: ClusterIP 15 | -------------------------------------------------------------------------------- /config/rbac/cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: ack-acm-controller-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: ack-acm-controller 9 | subjects: 10 | - kind: ServiceAccount 11 | name: ack-acm-controller 12 | namespace: ack-system 13 | -------------------------------------------------------------------------------- /templates/hooks/certificate/sdk_create_pre_build_request.go.tpl: -------------------------------------------------------------------------------- 1 | created, isImport, err := rm.maybeImportCertificate(ctx, desired) 2 | if err != nil { 3 | return nil, err 4 | } 5 | if isImport { 6 | return created, nil 7 | } 8 | if err = validatePublicValidationOptions(desired); err != nil { 9 | return nil, ackerr.NewTerminalError(err) 10 | } 11 | -------------------------------------------------------------------------------- /test/e2e/resources/certificate_public.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: acm.services.k8s.aws/v1alpha1 2 | kind: Certificate 3 | metadata: 4 | name: $CERTIFICATE_NAME 5 | spec: 6 | domainName: $DOMAIN_NAME 7 | # NOTE(jaypipes): Having an empty certificateAuthorityARN field indicates 8 | # that this is a public certificate request... 9 | tags: 10 | - key: environment 11 | value: dev 12 | -------------------------------------------------------------------------------- /OWNERS_ALIASES: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners#owners_aliases 2 | 3 | aliases: 4 | core-ack-team: 5 | - a-hilaly 6 | - jlbutler 7 | - michaelhtm 8 | - rushmash91 9 | - knottnt 10 | # emeritus-core-ack-team: 11 | # - TiberiuGC 12 | # - jaypipes 13 | # - jljaco 14 | # - mhausenblas 15 | # - RedbackThomson 16 | # - vijtrip2 17 | # - ivelichkovich -------------------------------------------------------------------------------- /config/rbac/leader-election-role-binding.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: RoleBinding 4 | metadata: 5 | namespace: ack-system 6 | name: acm-leader-election-rolebinding 7 | roleRef: 8 | apiGroup: rbac.authorization.k8s.io 9 | kind: Role 10 | name: acm-leader-election-role 11 | subjects: 12 | - kind: ServiceAccount 13 | name: ack-acm-controller 14 | namespace: ack-system 15 | -------------------------------------------------------------------------------- /test/e2e/resources/certificate_imported.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: acm.services.k8s.aws/v1alpha1 2 | kind: Certificate 3 | metadata: 4 | name: $CERTIFICATE_NAME 5 | spec: 6 | privateKey: 7 | name: $CERTIFICATE_NAME 8 | key: tls.key 9 | certificate: 10 | name: $CERTIFICATE_NAME 11 | key: tls.crt 12 | tags: 13 | - key: environment 14 | value: dev 15 | - key: imported 16 | value: "true" 17 | -------------------------------------------------------------------------------- /config/overlays/namespaced/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ../../default 3 | patches: 4 | - path: role.json 5 | target: 6 | group: rbac.authorization.k8s.io 7 | version: v1 8 | kind: ClusterRole 9 | name: ack-acm-controller 10 | - path: role-binding.json 11 | target: 12 | group: rbac.authorization.k8s.io 13 | version: v1 14 | kind: ClusterRoleBinding 15 | name: ack-acm-controller-rolebinding -------------------------------------------------------------------------------- /config/rbac/leader-election-role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: acm-leader-election-role 6 | namespace: ack-system 7 | rules: 8 | - apiGroups: 9 | - coordination.k8s.io 10 | resources: 11 | - leases 12 | verbs: 13 | - get 14 | - list 15 | - watch 16 | - create 17 | - update 18 | - patch 19 | - delete 20 | - apiGroups: 21 | - "" 22 | resources: 23 | - events 24 | verbs: 25 | - create 26 | - patch 27 | -------------------------------------------------------------------------------- /apis/v1alpha1/ack-generate-metadata.yaml: -------------------------------------------------------------------------------- 1 | ack_generate_info: 2 | build_date: "2025-12-02T21:07:23Z" 3 | build_hash: 06bffb95177cf873ee1b1a1c6f93cb30380c1e36 4 | go_version: go1.25.1 5 | version: v0.56.0-2-g06bffb9 6 | api_directory_checksum: 5dc0b682f154f3479809e330d2760ff9575e9bea 7 | api_version: v1alpha1 8 | aws_sdk_go_version: v1.32.6 9 | generator_config_info: 10 | file_checksum: 62b614b540cda719ee9142cbc530d180cbf2c52d 11 | original_file_name: generator.yaml 12 | last_modification: 13 | reason: API generation 14 | -------------------------------------------------------------------------------- /config/rbac/role-writer.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | creationTimestamp: null 6 | name: ack-acm-writer 7 | namespace: default 8 | rules: 9 | - apiGroups: 10 | - acm.services.k8s.aws 11 | resources: 12 | - certificates 13 | verbs: 14 | - create 15 | - delete 16 | - get 17 | - list 18 | - patch 19 | - update 20 | - watch 21 | - apiGroups: 22 | - acm.services.k8s.aws 23 | resources: 24 | - certificates 25 | verbs: 26 | - get 27 | - patch 28 | - update 29 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | # namespace: 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | # namePrefix: 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | resources: 16 | - ../crd 17 | - ../rbac 18 | - ../controller 19 | 20 | patchesStrategicMerge: 21 | -------------------------------------------------------------------------------- /test/e2e/resources/certificate_public_invalid.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: acm.services.k8s.aws/v1alpha1 2 | kind: Certificate 3 | metadata: 4 | name: $CERTIFICATE_NAME 5 | spec: 6 | domainName: $DOMAIN_NAME 7 | certificateAuthorityARN: invalid 8 | domainValidationOptions: 9 | - domainName: $DOMAIN_NAME 10 | - domainName: $DOMAIN_NAME 11 | - domainName: $DOMAIN_NAME 12 | - domainName: $DOMAIN_NAME 13 | - domainName: $DOMAIN_NAME 14 | - domainName: $DOMAIN_NAME 15 | - domainName: $DOMAIN_NAME 16 | tags: 17 | - key: environment 18 | value: dev 19 | -------------------------------------------------------------------------------- /templates/hooks/certificate/late_initialize_post_read_one.go.tpl: -------------------------------------------------------------------------------- 1 | { 2 | observedKo := rm.concreteResource(observed).ko 3 | latestKo := rm.concreteResource(latestCopy).ko 4 | if observedKo.Spec.DomainValidationOptions != nil && latestKo.Spec.DomainValidationOptions == nil { 5 | latestKo.Spec.DomainValidationOptions = observedKo.Spec.DomainValidationOptions 6 | } 7 | if observedKo.Spec.SubjectAlternativeNames != nil && latestKo.Spec.SubjectAlternativeNames == nil { 8 | latestKo.Spec.SubjectAlternativeNames = observedKo.Spec.SubjectAlternativeNames 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/e2e/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash # Use bash syntax 2 | 3 | # Set up variables 4 | GO111MODULE=on 5 | 6 | # Build ldflags 7 | VERSION ?= "v0.0.0" 8 | GITCOMMIT=$(shell git rev-parse HEAD) 9 | BUILDDATE=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ') 10 | GO_LDFLAGS=-ldflags "-X main.version=$(VERSION) \ 11 | -X main.buildHash=$(GITCOMMIT) \ 12 | -X main.buildDate=$(BUILDDATE)" 13 | 14 | .PHONY: all test 15 | 16 | all: test 17 | 18 | test: ## Run code tests 19 | go test -v ./... 20 | 21 | help: ## Show this help. 22 | @grep -F -h "##" $(MAKEFILE_LIST) | grep -F -v grep | sed -e 's/\\$$//' \ 23 | | awk -F'[:#]' '{print $$1 = sprintf("%-30s", $$1), $$4}' 24 | -------------------------------------------------------------------------------- /helm/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | name: acm-chart 3 | description: A Helm chart for the ACK service controller for AWS Certificate Manager (ACM) 4 | version: 1.3.0 5 | appVersion: 1.3.0 6 | home: https://github.com/aws-controllers-k8s/acm-controller 7 | icon: https://raw.githubusercontent.com/aws/eks-charts/master/docs/logo/aws.png 8 | sources: 9 | - https://github.com/aws-controllers-k8s/acm-controller 10 | maintainers: 11 | - name: ACK Admins 12 | url: https://github.com/orgs/aws-controllers-k8s/teams/ack-admin 13 | - name: ACM Admins 14 | url: https://github.com/orgs/aws-controllers-k8s/teams/acm-maintainer 15 | keywords: 16 | - aws 17 | - kubernetes 18 | - acm 19 | -------------------------------------------------------------------------------- /documentation.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | Certificate: 3 | fields: 4 | Certificate: 5 | prepend: | 6 | The Certificate to import into AWS Certificate Manager (ACM) to use with services that are integrated with ACM. 7 | This field is only valid when importing an existing certificate into ACM. 8 | PrivateKey: 9 | prepend: | 10 | The private key that matches the public key in the certificate. This field is only valid when importing 11 | an existing certificate into ACM. 12 | CertificateARN: 13 | prepend: | 14 | The Amazon Resource Name (ARN) of an imported certificate to replace. This field is only valid when importing 15 | an existing certificate into ACM. 16 | -------------------------------------------------------------------------------- /test/e2e/replacement_values.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | """Stores the values used by each of the integration tests for replacing the 14 | ACM-specific test variables. 15 | """ 16 | 17 | REPLACEMENT_VALUES = { 18 | } 19 | -------------------------------------------------------------------------------- /helm/templates/role-reader.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | creationTimestamp: null 6 | name: {{ include "ack-acm-controller.app.fullname" . }}-reader 7 | namespace: {{ .Release.Namespace }} 8 | labels: 9 | app.kubernetes.io/name: {{ include "ack-acm-controller.app.name" . }} 10 | app.kubernetes.io/instance: {{ .Release.Name }} 11 | app.kubernetes.io/managed-by: Helm 12 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 13 | k8s-app: {{ include "ack-acm-controller.app.name" . }} 14 | helm.sh/chart: {{ include "ack-acm-controller.chart.name-version" . }} 15 | rules: 16 | - apiGroups: 17 | - acm.services.k8s.aws 18 | resources: 19 | - certificates 20 | verbs: 21 | - get 22 | - list 23 | - watch 24 | -------------------------------------------------------------------------------- /helm/templates/service-account.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create }} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: {{ include "ack-acm-controller.app.name" . }} 7 | app.kubernetes.io/instance: {{ .Release.Name }} 8 | app.kubernetes.io/managed-by: Helm 9 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 10 | k8s-app: {{ include "ack-acm-controller.app.name" . }} 11 | helm.sh/chart: {{ include "ack-acm-controller.chart.name-version" . }} 12 | name: {{ include "ack-acm-controller.service-account.name" . }} 13 | namespace: {{ .Release.Namespace }} 14 | annotations: 15 | {{- range $key, $value := .Values.serviceAccount.annotations }} 16 | {{ $key }}: {{ $value | quote }} 17 | {{- end }} 18 | {{- end }} 19 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package version 17 | 18 | var ( 19 | GitVersion string 20 | GitCommit string 21 | BuildDate string 22 | ) 23 | -------------------------------------------------------------------------------- /helm/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | {{ .Chart.Name }} has been installed. 2 | This chart deploys "public.ecr.aws/aws-controllers-k8s/acm-controller:1.3.0". 3 | 4 | Check its status by running: 5 | kubectl --namespace {{ .Release.Namespace }} get pods -l "app.kubernetes.io/instance={{ .Release.Name }}" 6 | 7 | You are now able to create AWS Certificate Manager (ACM) resources! 8 | 9 | The controller is running in "{{ .Values.installScope }}" mode. 10 | The controller is configured to manage AWS resources in region: "{{ .Values.aws.region }}" 11 | 12 | Visit https://aws-controllers-k8s.github.io/community/reference/ for an API 13 | reference of all the resources that can be created using this controller. 14 | 15 | For more information on the AWS Controllers for Kubernetes (ACK) project, visit: 16 | https://aws-controllers-k8s.github.io/community/ 17 | -------------------------------------------------------------------------------- /templates/hooks/certificate/sdk_create_post_build_request.go.tpl: -------------------------------------------------------------------------------- 1 | // We only support DNS-based validation, because 2 | // certificate renewal is not really automatable when email verification 3 | // is used. 4 | // 5 | // See discussion here: 6 | // https://docs.aws.amazon.com/acm/latest/userguide/email-validation.html 7 | // 8 | // Unfortunately, because fields in the "ignore" configuration list are 9 | // now deleted from the aws-sdk-go private/model/api.Shape object, 10 | // setting `override_values` does not work. 11 | 12 | input.ValidationMethod = "DNS" 13 | 14 | // NOTE: exportPreference can ONLY be set for public certificates 15 | if desired.ko.Spec.ExportTo != nil && desired.ko.Spec.CertificateAuthorityARN == nil && desired.ko.Spec.CertificateAuthorityRef == nil { 16 | if input.Options == nil { 17 | input.Options = &svcsdktypes.CertificateOptions{} 18 | } 19 | input.Options.Export = "ENABLED" 20 | } -------------------------------------------------------------------------------- /helm/templates/role-writer.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | creationTimestamp: null 6 | name: {{ include "ack-acm-controller.app.fullname" . }}-writer 7 | namespace: {{ .Release.Namespace }} 8 | labels: 9 | app.kubernetes.io/name: {{ include "ack-acm-controller.app.name" . }} 10 | app.kubernetes.io/instance: {{ .Release.Name }} 11 | app.kubernetes.io/managed-by: Helm 12 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 13 | k8s-app: {{ include "ack-acm-controller.app.name" . }} 14 | helm.sh/chart: {{ include "ack-acm-controller.chart.name-version" . }} 15 | rules: 16 | - apiGroups: 17 | - acm.services.k8s.aws 18 | resources: 19 | - certificates 20 | verbs: 21 | - create 22 | - delete 23 | - get 24 | - list 25 | - patch 26 | - update 27 | - watch 28 | - apiGroups: 29 | - acm.services.k8s.aws 30 | resources: 31 | - certificates 32 | verbs: 33 | - get 34 | - patch 35 | - update 36 | -------------------------------------------------------------------------------- /test/e2e/service_cleanup.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | """Cleans up the resources created by the bootstrapping process. 15 | """ 16 | 17 | import logging 18 | 19 | from acktest.bootstrapping import Resources 20 | 21 | from e2e import bootstrap_directory 22 | 23 | def service_cleanup(): 24 | logging.getLogger().setLevel(logging.INFO) 25 | 26 | resources = Resources.deserialize(bootstrap_directory) 27 | resources.cleanup() 28 | 29 | if __name__ == "__main__": 30 | service_cleanup() 31 | -------------------------------------------------------------------------------- /config/iam/recommended-inline-policy: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "ACMPublicCertificatePermissions", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "acm:DescribeCertificate", 9 | "acm:ImportCertificate", 10 | "acm:RequestCertificate", 11 | "acm:UpdateCertificateOptions", 12 | "acm:DeleteCertificate", 13 | "acm:AddTagsToCertificate", 14 | "acm:RemoveTagsFromCertificate", 15 | "acm:ListTagsForCertificate", 16 | "acm:ExportCertificate" 17 | ], 18 | "Resource": "*" 19 | }, 20 | { 21 | "Sid": "ACMPrivateCertificatePermissions", 22 | "Effect": "Allow", 23 | "Action": [ 24 | "acm-pca:IssueCertificate", 25 | "acm-pca:GetCertificate", 26 | "acm-pca:ListPermissions" 27 | ], 28 | "Resource": "*" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /helm/templates/leader-election-role.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.leaderElection.enabled }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: {{ include "ack-acm-controller.app.fullname" . }}-leaderelection 6 | {{ if .Values.leaderElection.namespace }} 7 | namespace: {{ .Values.leaderElection.namespace }} 8 | {{ else }} 9 | namespace: {{ .Release.Namespace }} 10 | {{ end }} 11 | labels: 12 | app.kubernetes.io/name: {{ include "ack-acm-controller.app.name" . }} 13 | app.kubernetes.io/instance: {{ .Release.Name }} 14 | app.kubernetes.io/managed-by: Helm 15 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 16 | k8s-app: {{ include "ack-acm-controller.app.name" . }} 17 | helm.sh/chart: {{ include "ack-acm-controller.chart.name-version" . }} 18 | rules: 19 | - apiGroups: 20 | - coordination.k8s.io 21 | resources: 22 | - leases 23 | verbs: 24 | - get 25 | - list 26 | - watch 27 | - create 28 | - update 29 | - patch 30 | - delete 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - events 35 | verbs: 36 | - create 37 | - patch{{- end }} 38 | -------------------------------------------------------------------------------- /helm/templates/leader-election-role-binding.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.leaderElection.enabled }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: RoleBinding 4 | metadata: 5 | name: {{ include "ack-acm-controller.app.fullname" . }}-leaderelection 6 | {{ if .Values.leaderElection.namespace }} 7 | namespace: {{ .Values.leaderElection.namespace }} 8 | {{ else }} 9 | namespace: {{ .Release.Namespace }} 10 | {{ end }} 11 | labels: 12 | app.kubernetes.io/name: {{ include "ack-acm-controller.app.name" . }} 13 | app.kubernetes.io/instance: {{ .Release.Name }} 14 | app.kubernetes.io/managed-by: Helm 15 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 16 | k8s-app: {{ include "ack-acm-controller.app.name" . }} 17 | helm.sh/chart: {{ include "ack-acm-controller.chart.name-version" . }} 18 | roleRef: 19 | apiGroup: rbac.authorization.k8s.io 20 | kind: Role 21 | name: {{ include "ack-acm-controller.app.fullname" . }}-leaderelection 22 | subjects: 23 | - kind: ServiceAccount 24 | name: {{ include "ack-acm-controller.service-account.name" . }} 25 | namespace: {{ .Release.Namespace }}{{- end }} 26 | -------------------------------------------------------------------------------- /helm/templates/metrics-service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.metrics.service.create }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ .Chart.Name | trimSuffix "-chart" | trunc 44 }}-controller-metrics 6 | namespace: {{ .Release.Namespace }} 7 | labels: 8 | app.kubernetes.io/name: {{ include "ack-acm-controller.app.name" . }} 9 | app.kubernetes.io/instance: {{ .Release.Name }} 10 | app.kubernetes.io/managed-by: Helm 11 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 12 | k8s-app: {{ include "ack-acm-controller.app.name" . }} 13 | helm.sh/chart: {{ include "ack-acm-controller.chart.name-version" . }} 14 | spec: 15 | selector: 16 | app.kubernetes.io/name: {{ include "ack-acm-controller.app.name" . }} 17 | app.kubernetes.io/instance: {{ .Release.Name }} 18 | app.kubernetes.io/managed-by: Helm 19 | k8s-app: {{ include "ack-acm-controller.app.name" . }} 20 | {{- range $key, $value := .Values.deployment.labels }} 21 | {{ $key }}: {{ $value | quote }} 22 | {{- end }} 23 | type: {{ .Values.metrics.service.type }} 24 | ports: 25 | - name: metricsport 26 | port: 8080 27 | targetPort: http 28 | protocol: TCP 29 | {{- end }} 30 | -------------------------------------------------------------------------------- /apis/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package v1alpha1 17 | 18 | import ( 19 | "k8s.io/apimachinery/pkg/runtime/schema" 20 | "sigs.k8s.io/controller-runtime/pkg/scheme" 21 | ) 22 | 23 | var ( 24 | // GroupVersion is the API Group Version used to register the objects 25 | GroupVersion = schema.GroupVersion{Group: "acm.services.k8s.aws", Version: "v1alpha1"} 26 | 27 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 28 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 29 | 30 | // AddToScheme adds the types in this group-version to the given scheme. 31 | AddToScheme = SchemeBuilder.AddToScheme 32 | ) 33 | -------------------------------------------------------------------------------- /test/e2e/bootstrap_resources.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | """Declares the structure of the bootstrapped resources and provides a loader 15 | for them. 16 | """ 17 | 18 | from dataclasses import dataclass 19 | from acktest.bootstrapping import Resources 20 | from e2e import bootstrap_directory 21 | 22 | @dataclass 23 | class BootstrapResources(Resources): 24 | pass 25 | 26 | _bootstrap_resources = None 27 | 28 | def get_bootstrap_resources(bootstrap_file_name: str = "bootstrap.pkl") -> BootstrapResources: 29 | global _bootstrap_resources 30 | if _bootstrap_resources is None: 31 | _bootstrap_resources = BootstrapResources.deserialize(bootstrap_directory, bootstrap_file_name=bootstrap_file_name) 32 | return _bootstrap_resources 33 | -------------------------------------------------------------------------------- /config/rbac/cluster-role-controller.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: ack-acm-controller 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | - secrets 12 | verbs: 13 | - get 14 | - list 15 | - patch 16 | - watch 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - namespaces 21 | verbs: 22 | - get 23 | - list 24 | - watch 25 | - apiGroups: 26 | - acm.services.k8s.aws 27 | resources: 28 | - certificates 29 | verbs: 30 | - create 31 | - delete 32 | - get 33 | - list 34 | - patch 35 | - update 36 | - watch 37 | - apiGroups: 38 | - acm.services.k8s.aws 39 | resources: 40 | - certificates/status 41 | verbs: 42 | - get 43 | - patch 44 | - update 45 | - apiGroups: 46 | - acmpca.services.k8s.aws 47 | resources: 48 | - certificateauthorities 49 | - certificateauthorities/status 50 | verbs: 51 | - get 52 | - list 53 | - apiGroups: 54 | - services.k8s.aws 55 | resources: 56 | - fieldexports 57 | - iamroleselectors 58 | verbs: 59 | - create 60 | - delete 61 | - get 62 | - list 63 | - patch 64 | - update 65 | - watch 66 | - apiGroups: 67 | - services.k8s.aws 68 | resources: 69 | - fieldexports/status 70 | - iamroleselectors/status 71 | verbs: 72 | - get 73 | - patch 74 | - update 75 | -------------------------------------------------------------------------------- /test/e2e/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | import pytest 15 | from typing import Dict, Any 16 | from pathlib import Path 17 | 18 | from acktest.resources import load_resource_file 19 | 20 | SERVICE_NAME = "acm" 21 | CRD_GROUP = "acm.services.k8s.aws" 22 | CRD_VERSION = "v1alpha1" 23 | 24 | # PyTest marker for the current service 25 | service_marker = pytest.mark.service(arg=SERVICE_NAME) 26 | 27 | bootstrap_directory = Path(__file__).parent 28 | resource_directory = Path(__file__).parent / "resources" 29 | 30 | def load_resource(resource_name: str, additional_replacements: Dict[str, Any] = {}): 31 | """ Overrides the default `load_resource_file` to access the specific resources 32 | directory for the current service. 33 | """ 34 | return load_resource_file(resource_directory, resource_name, additional_replacements=additional_replacements) 35 | -------------------------------------------------------------------------------- /helm/templates/caches-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: {{ include "ack-acm-controller.app.fullname" . }}-namespaces-cache 5 | labels: 6 | app.kubernetes.io/name: {{ include "ack-acm-controller.app.name" . }} 7 | app.kubernetes.io/instance: {{ .Release.Name }} 8 | app.kubernetes.io/managed-by: Helm 9 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 10 | k8s-app: {{ include "ack-acm-controller.app.name" . }} 11 | helm.sh/chart: {{ include "ack-acm-controller.chart.name-version" . }} 12 | rules: 13 | - apiGroups: 14 | - "" 15 | resources: 16 | - namespaces 17 | verbs: 18 | - get 19 | - list 20 | - watch 21 | --- 22 | apiVersion: rbac.authorization.k8s.io/v1 23 | kind: Role 24 | metadata: 25 | name: {{ include "ack-acm-controller.app.fullname" . }}-configmaps-cache 26 | namespace: {{ .Release.Namespace }} 27 | labels: 28 | app.kubernetes.io/name: {{ include "ack-acm-controller.app.name" . }} 29 | app.kubernetes.io/instance: {{ .Release.Name }} 30 | app.kubernetes.io/managed-by: Helm 31 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 32 | k8s-app: {{ include "ack-acm-controller.app.name" . }} 33 | helm.sh/chart: {{ include "ack-acm-controller.chart.name-version" . }} 34 | rules: 35 | - apiGroups: 36 | - "" 37 | resources: 38 | - configmaps 39 | verbs: 40 | - get 41 | - list 42 | - watch -------------------------------------------------------------------------------- /test/e2e/service_bootstrap.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | """Bootstraps the resources required to run the ACM integration tests. 14 | """ 15 | import logging 16 | 17 | from acktest.bootstrapping import Resources, BootstrapFailureException 18 | 19 | from e2e import bootstrap_directory 20 | from e2e.bootstrap_resources import BootstrapResources 21 | 22 | def service_bootstrap() -> Resources: 23 | logging.getLogger().setLevel(logging.INFO) 24 | 25 | resources = BootstrapResources( 26 | # TODO: Add bootstrapping when you have defined the resources 27 | ) 28 | 29 | try: 30 | resources.bootstrap() 31 | except BootstrapFailureException as ex: 32 | exit(254) 33 | 34 | return resources 35 | 36 | if __name__ == "__main__": 37 | config = service_bootstrap() 38 | # Write config to current directory by default 39 | config.serialize(bootstrap_directory) 40 | -------------------------------------------------------------------------------- /test/e2e/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | import boto3 15 | import pytest 16 | 17 | from acktest import k8s 18 | 19 | def pytest_addoption(parser): 20 | parser.addoption("--runslow", action="store_true", default=False, help="run slow tests") 21 | 22 | def pytest_configure(config): 23 | config.addinivalue_line( 24 | "markers", "service(arg): mark test associated with a given service" 25 | ) 26 | config.addinivalue_line( 27 | "markers", "slow: mark test as slow to run" 28 | ) 29 | 30 | def pytest_collection_modifyitems(config, items): 31 | if config.getoption("--runslow"): 32 | return 33 | skip_slow = pytest.mark.skip(reason="need --runslow option to run") 34 | for item in items: 35 | if "slow" in item.keywords: 36 | item.add_marker(skip_slow) 37 | 38 | # Provide a k8s client to interact with the integration test cluster 39 | @pytest.fixture(scope='class') 40 | def k8s_client(): 41 | return k8s._get_k8s_api_client() 42 | 43 | @pytest.fixture(scope='module') 44 | def acm_client(): 45 | return boto3.client('acm') 46 | -------------------------------------------------------------------------------- /test/e2e/x509.py: -------------------------------------------------------------------------------- 1 | from cryptography.hazmat.primitives import serialization 2 | from cryptography.hazmat.primitives import hashes 3 | from cryptography.hazmat.primitives.asymmetric import rsa 4 | from cryptography import x509 5 | from cryptography.x509.oid import NameOID 6 | import datetime 7 | 8 | # https://cryptography.io/en/latest/x509/tutorial/#creating-a-self-signed-certificate 9 | def create_x509_certificate(org_name: str, common_name: str, san: str): 10 | key = rsa.generate_private_key( 11 | public_exponent=65537, 12 | key_size=2048, 13 | ) 14 | subject = issuer = x509.Name([ 15 | x509.NameAttribute(NameOID.ORGANIZATION_NAME, org_name), 16 | x509.NameAttribute(NameOID.COMMON_NAME, common_name), 17 | ]) 18 | cert = x509.CertificateBuilder().subject_name( 19 | subject 20 | ).issuer_name( 21 | issuer 22 | ).public_key( 23 | key.public_key() 24 | ).serial_number( 25 | x509.random_serial_number() 26 | ).not_valid_before( 27 | datetime.datetime.now(datetime.timezone.utc) 28 | ).not_valid_after( 29 | datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=1) 30 | ).add_extension( 31 | x509.SubjectAlternativeName([x509.DNSName(san)]), 32 | critical=False, 33 | ).sign(key, hashes.SHA256()) 34 | 35 | private_key = key.private_bytes( 36 | encoding=serialization.Encoding.PEM, 37 | format=serialization.PrivateFormat.TraditionalOpenSSL, 38 | encryption_algorithm=serialization.NoEncryption(), 39 | ) 40 | certificate = cert.public_bytes(serialization.Encoding.PEM) 41 | return private_key, certificate 42 | -------------------------------------------------------------------------------- /templates/hooks/certificate/sdk_read_one_pre_set_output.go.tpl: -------------------------------------------------------------------------------- 1 | if resp.Certificate.DomainValidationOptions != nil { 2 | dvs := []*svcapitypes.DomainValidation{} 3 | for _, dvsiter := range resp.Certificate.DomainValidationOptions { 4 | dvselem := &svcapitypes.DomainValidation{} 5 | if dvsiter.DomainName != nil { 6 | dvselem.DomainName = dvsiter.DomainName 7 | } 8 | if dvsiter.ResourceRecord != nil { 9 | dvselem.ResourceRecord = &svcapitypes.ResourceRecord{} 10 | if dvsiter.ResourceRecord.Name != nil { 11 | dvselem.ResourceRecord.Name = dvsiter.ResourceRecord.Name 12 | } 13 | if dvsiter.ResourceRecord.Type != "" { 14 | dvselem.ResourceRecord.Type = aws.String(string(dvsiter.ResourceRecord.Type)) 15 | } 16 | if dvsiter.ResourceRecord.Value != nil { 17 | dvselem.ResourceRecord.Value = dvsiter.ResourceRecord.Value 18 | } 19 | } 20 | if dvsiter.ValidationDomain != nil { 21 | dvselem.ValidationDomain = dvsiter.ValidationDomain 22 | } 23 | for _, ve := range dvsiter.ValidationEmails { 24 | dvselem.ValidationEmails = append(dvselem.ValidationEmails, &ve) 25 | } 26 | if dvsiter.ValidationMethod != "" { 27 | dvselem.ValidationMethod = aws.String(string(dvsiter.ValidationMethod)) 28 | } 29 | if dvsiter.ValidationStatus != "" { 30 | dvselem.ValidationStatus = aws.String(string(dvsiter.ValidationStatus)) 31 | } 32 | dvs = append(dvs, dvselem) 33 | } 34 | ko.Status.DomainValidations = dvs 35 | } else { 36 | ko.Status.DomainValidations = nil 37 | } 38 | ko.Spec.Tags, err = listTags( 39 | ctx, rm.sdkapi, rm.metrics, 40 | string(*r.ko.Status.ACKResourceMetadata.ARN), 41 | ) 42 | if err != nil { 43 | return nil, err 44 | } -------------------------------------------------------------------------------- /helm/templates/cluster-role-controller.yaml: -------------------------------------------------------------------------------- 1 | {{ $labels := .Values.role.labels }} 2 | {{ $appVersion := .Chart.AppVersion | quote }} 3 | {{ $rbacRules := include "ack-acm-controller.rbac-rules" . }} 4 | {{ $fullname := include "ack-acm-controller.app.fullname" . }} 5 | {{ $chartVersion := include "ack-acm-controller.chart.name-version" . }} 6 | {{ if eq .Values.installScope "cluster" }} 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRole 9 | metadata: 10 | name: {{ include "ack-acm-controller.app.fullname" . }} 11 | labels: 12 | app.kubernetes.io/name: {{ include "ack-acm-controller.app.name" . }} 13 | app.kubernetes.io/instance: {{ .Release.Name }} 14 | app.kubernetes.io/managed-by: Helm 15 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 16 | k8s-app: {{ include "ack-acm-controller.app.name" . }} 17 | helm.sh/chart: {{ include "ack-acm-controller.chart.name-version" . }} 18 | {{- range $key, $value := $labels }} 19 | {{ $key }}: {{ $value | quote }} 20 | {{- end }} 21 | {{$rbacRules }} 22 | {{ else if eq .Values.installScope "namespace" }} 23 | {{ $wn := include "ack-acm-controller.watch-namespace" . }} 24 | {{ $namespaces := split "," $wn }} 25 | {{ range $namespaces }} 26 | --- 27 | apiVersion: rbac.authorization.k8s.io/v1 28 | kind: Role 29 | metadata: 30 | name: {{ $fullname }}-{{ . }} 31 | namespace: {{ . }} 32 | labels: 33 | app.kubernetes.io/name: {{ $fullname }} 34 | app.kubernetes.io/instance: {{ $.Release.Name }} 35 | app.kubernetes.io/managed-by: Helm 36 | app.kubernetes.io/version: {{ $appVersion }} 37 | k8s-app: {{ $fullname }} 38 | helm.sh/chart: {{ $chartVersion }} 39 | {{- range $key, $value := $labels }} 40 | {{ $key }}: {{ $value | quote }} 41 | {{- end }} 42 | {{ $rbacRules }} 43 | {{ end }} 44 | {{ end }} -------------------------------------------------------------------------------- /helm/templates/caches-role-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: {{ include "ack-acm-controller.app.fullname" . }}-namespaces-cache 5 | labels: 6 | app.kubernetes.io/name: {{ include "ack-acm-controller.app.name" . }} 7 | app.kubernetes.io/instance: {{ .Release.Name }} 8 | app.kubernetes.io/managed-by: Helm 9 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 10 | k8s-app: {{ include "ack-acm-controller.app.name" . }} 11 | helm.sh/chart: {{ include "ack-acm-controller.chart.name-version" . }} 12 | roleRef: 13 | kind: ClusterRole 14 | apiGroup: rbac.authorization.k8s.io 15 | name: {{ include "ack-acm-controller.app.fullname" . }}-namespaces-cache 16 | subjects: 17 | - kind: ServiceAccount 18 | name: {{ include "ack-acm-controller.service-account.name" . }} 19 | namespace: {{ .Release.Namespace }} 20 | --- 21 | apiVersion: rbac.authorization.k8s.io/v1 22 | kind: RoleBinding 23 | metadata: 24 | name: {{ include "ack-acm-controller.app.fullname" . }}-configmaps-cache 25 | namespace: {{ .Release.Namespace }} 26 | labels: 27 | app.kubernetes.io/name: {{ include "ack-acm-controller.app.name" . }} 28 | app.kubernetes.io/instance: {{ .Release.Name }} 29 | app.kubernetes.io/managed-by: Helm 30 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 31 | k8s-app: {{ include "ack-acm-controller.app.name" . }} 32 | helm.sh/chart: {{ include "ack-acm-controller.chart.name-version" . }} 33 | roleRef: 34 | kind: Role 35 | apiGroup: rbac.authorization.k8s.io 36 | name: {{ include "ack-acm-controller.app.fullname" . }}-configmaps-cache 37 | subjects: 38 | - kind: ServiceAccount 39 | name: {{ include "ack-acm-controller.service-account.name" . }} 40 | namespace: {{ .Release.Namespace }} 41 | -------------------------------------------------------------------------------- /templates/hooks/certificate/sdk_file_end.go.tpl: -------------------------------------------------------------------------------- 1 | {{ $CRD := .CRD }} 2 | {{ $SDKAPI := .SDKAPI }} 3 | 4 | {{/* Maintain operations here */}} 5 | {{ range $operationName := Each "ImportCertificate" }} 6 | 7 | {{- $operation := (index $SDKAPI.API.Operations $operationName)}} 8 | 9 | {{- $inputRef := $operation.InputRef }} 10 | {{- $inputShapeName := $inputRef.ShapeName }} 11 | 12 | {{- $outputRef := $operation.OutputRef }} 13 | {{- $outputShapeName := $outputRef.ShapeName }} 14 | 15 | 16 | {{/* Some operations have custom structure */}} 17 | {{- if (eq $operationName "ImportCertificate") }} 18 | 19 | // new{{ $inputShapeName }} returns a {{ $inputShapeName }} object 20 | // with each field set by the corresponding configuration's fields. 21 | func (rm *resourceManager) new{{ $inputShapeName }}( 22 | ctx context.Context, 23 | r *resource, 24 | ) (*svcsdk.{{ $inputShapeName }}, error) { 25 | input := &importCertificateInput{ImportCertificateInput: &svcsdk.ImportCertificateInput{}} 26 | {{ GoCodeSetSDKForStruct $CRD "" "input" $inputRef "" "r.ko.Spec" 1 }} 27 | {{range $fieldName := Each "PrivateKey" "Certificate" "CertificateChain"}} 28 | { 29 | tmpSecret, err := rm.rr.SecretValueFromReference(ctx, r.ko.Spec.{{$fieldName}}) 30 | if err != nil { 31 | return nil, ackrequeue.Needed(err) 32 | } 33 | if tmpSecret != "" { 34 | input.ImportCertificateInput.{{$fieldName}} = []byte(tmpSecret) 35 | } 36 | } 37 | {{end}} 38 | return input.ImportCertificateInput, nil 39 | } 40 | {{ end }} 41 | 42 | // setResourceFrom{{ $outputShapeName }} sets a resource {{ $outputShapeName }} type 43 | // given the SDK type. 44 | func (rm *resourceManager) setResourceFrom{{ $outputShapeName }}( 45 | r *resource, 46 | resp *svcsdk.{{ $outputShapeName }}, 47 | ) { 48 | {{ GoCodeSetCreateOutput $CRD "resp" "r.ko" 1 }} 49 | } 50 | 51 | {{- end }} 52 | -------------------------------------------------------------------------------- /olm/olmconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is a placeholder. Replace any values with relevant values for your 2 | # service controller project. 3 | --- 4 | annotations: 5 | capabilityLevel: Basic Install 6 | shortDescription: AWS ACM controller is a service controller for managing ACM resources 7 | in Kubernetes 8 | displayName: AWS Controllers for Kubernetes - Amazon ACM 9 | description: |- 10 | Manage AWS Certificate Manager (ACM) resources in AWS from within your Kubernetes cluster. 11 | 12 | 13 | **About Amazon ACM** 14 | 15 | 16 | AWS Certificate Manager (ACM) handles the complexity of creating, storing, and renewing public and private SSL/TLS X.509 17 | certificates and keys that protect your AWS websites and applications. You can provide certificates for your 18 | [integrated AWS services](https://docs.aws.amazon.com/acm/latest/userguide/acm-services.html) either by issuing them 19 | directly with ACM or by [importing](https://docs.aws.amazon.com/acm/latest/userguide/import-certificate.html) third-party 20 | certificates into the ACM management system. ACM certificates can secure singular domain names, multiple specific 21 | domain names, wildcard domains, or combinations of these. ACM wildcard certificates can protect an unlimited number of 22 | subdomains. You can also [export](https://docs.aws.amazon.com/acm/latest/userguide/export-private.html) ACM certificates 23 | signed by AWS Private CA for use anywhere in your internal PKI. 24 | 25 | 26 | **About the AWS Controllers for Kubernetes** 27 | 28 | 29 | This controller is a component of the [AWS Controller for Kubernetes](https://github.com/aws/aws-controllers-k8s) 30 | project. This project is currently in **developer preview**. 31 | samples: 32 | - kind: Certificate 33 | spec: '{}' 34 | maintainers: 35 | - name: "acm maintainer team" 36 | email: "ack-maintainers@amazon.com" 37 | links: 38 | - name: Amazon ACM Developer Resources 39 | url: https://aws.amazon.com/certificate-manager/resources/ 40 | -------------------------------------------------------------------------------- /pkg/resource/certificate/identifiers.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package certificate 17 | 18 | import ( 19 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 20 | ) 21 | 22 | // resourceIdentifiers implements the 23 | // `aws-service-operator-k8s/pkg/types.AWSResourceIdentifiers` interface 24 | type resourceIdentifiers struct { 25 | meta *ackv1alpha1.ResourceMetadata 26 | } 27 | 28 | // ARN returns the AWS Resource Name for the backend AWS resource. If nil, 29 | // this means the resource has not yet been created in the backend AWS 30 | // service. 31 | func (ri *resourceIdentifiers) ARN() *ackv1alpha1.AWSResourceName { 32 | if ri.meta != nil { 33 | return ri.meta.ARN 34 | } 35 | return nil 36 | } 37 | 38 | // OwnerAccountID returns the AWS account identifier in which the 39 | // backend AWS resource resides, or nil if this information is not known 40 | // for the resource 41 | func (ri *resourceIdentifiers) OwnerAccountID() *ackv1alpha1.AWSAccountID { 42 | if ri.meta != nil { 43 | return ri.meta.OwnerAccountID 44 | } 45 | return nil 46 | } 47 | 48 | // Region returns the AWS region in which the resource exists, or 49 | // nil if this information is not known. 50 | func (ri *resourceIdentifiers) Region() *ackv1alpha1.AWSRegion { 51 | if ri.meta != nil { 52 | return ri.meta.Region 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /GOVERNANCE.md: -------------------------------------------------------------------------------- 1 | # Project governance 2 | 3 | This document lays out the guidelines under which the AWS Controllers for Kubernetes (ACK) project will be governed. 4 | The goal is to make sure that the roles and responsibilities are well defined and clarify on how decisions are made. 5 | 6 | ## Roles 7 | 8 | In the context of ACK, we consider the following roles: 9 | 10 | * __Users__ ... everyone using ACK, typically willing to provide feedback on ACK by proposing features and/or filing issues. 11 | * __Contributors__ ... everyone contributing code, documentation, examples, testing infra, and participating in feature proposals as well as design discussions. Code contributions will require a Developer Certificate of Origin (DCO). 12 | * __Maintainers__ ... are responsible for engaging with and assisting contributors to iterate on the contributions until it reaches acceptable quality. Maintainers can decide whether the contributions can be accepted into the project or rejected. Any active contributor meeting the project quality can be made a Maintainer by the Advisory Board. 13 | * __Advisory Board__ ... is responsible for defining the guidelines and processes that the project operates under. 14 | 15 | The initial members of the Advisory Board are `@jaypipes` and `@mhausenblas`. 16 | 17 | 18 | ## Communication 19 | 20 | The primary mechanism for communication will be via the `#provider-aws` channel on the Kubernetes Slack community. 21 | All features and bug fixes will be tracked as issues in GitHub. All decisions will be documented in GitHub issues. 22 | 23 | In the future, we may consider using a public mailing list, which can be better archived. 24 | 25 | ## Roadmap Planning 26 | 27 | Maintainers will share roadmap and release versions as milestones in GitHub. 28 | 29 | ## Release Management 30 | 31 | The Advisory Board will propose a release management proposal via a GitHub issue and resolve it there. 32 | 33 | ## Other relevant governance resources 34 | 35 | * The ACK [Contributing Guidelines](CONTRIBUTING.md) 36 | * Our [Code of Conduct](CODE_OF_CONDUCT.md) 37 | -------------------------------------------------------------------------------- /pkg/resource/registry.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package resource 17 | 18 | import ( 19 | ackrt "github.com/aws-controllers-k8s/runtime/pkg/runtime" 20 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 21 | ) 22 | 23 | // +kubebuilder:rbac:groups=services.k8s.aws,resources=iamroleselectors,verbs=get;list;watch;create;update;patch;delete 24 | // +kubebuilder:rbac:groups=services.k8s.aws,resources=iamroleselectors/status,verbs=get;update;patch 25 | // +kubebuilder:rbac:groups=services.k8s.aws,resources=fieldexports,verbs=get;list;watch;create;update;patch;delete 26 | // +kubebuilder:rbac:groups=services.k8s.aws,resources=fieldexports/status,verbs=get;update;patch 27 | // +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch 28 | // +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;patch 29 | // +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;patch 30 | 31 | var ( 32 | reg = ackrt.NewRegistry() 33 | ) 34 | 35 | // GetManagerFactories returns a slice of resource manager factories that are 36 | // registered with this package 37 | func GetManagerFactories() []acktypes.AWSResourceManagerFactory { 38 | return reg.GetResourceManagerFactories() 39 | } 40 | 41 | // RegisterManagerFactory registers a resource manager factory with the 42 | // package's registry 43 | func RegisterManagerFactory(f acktypes.AWSResourceManagerFactory) { 44 | reg.RegisterResourceManagerFactory(f) 45 | } 46 | -------------------------------------------------------------------------------- /helm/templates/cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | {{ if eq .Values.installScope "cluster" }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: {{ include "ack-acm-controller.app.fullname" . }}-rolebinding 6 | labels: 7 | app.kubernetes.io/name: {{ include "ack-acm-controller.app.name" . }} 8 | app.kubernetes.io/instance: {{ .Release.Name }} 9 | app.kubernetes.io/managed-by: Helm 10 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 11 | k8s-app: {{ include "ack-acm-controller.app.name" . }} 12 | helm.sh/chart: {{ include "ack-acm-controller.chart.name-version" . }} 13 | roleRef: 14 | kind: ClusterRole 15 | apiGroup: rbac.authorization.k8s.io 16 | name: {{ include "ack-acm-controller.app.fullname" . }} 17 | subjects: 18 | - kind: ServiceAccount 19 | name: {{ include "ack-acm-controller.service-account.name" . }} 20 | namespace: {{ .Release.Namespace }} 21 | {{ else if eq .Values.installScope "namespace" }} 22 | {{ $wn := include "ack-acm-controller.watch-namespace" . }} 23 | {{ $namespaces := split "," $wn }} 24 | {{ $fullname := include "ack-acm-controller.app.fullname" . }} 25 | {{ $releaseNamespace := .Release.Namespace }} 26 | {{ $serviceAccountName := include "ack-acm-controller.service-account.name" . }} 27 | {{ $chartVersion := include "ack-acm-controller.chart.name-version" . }} 28 | {{ $appVersion := .Chart.AppVersion | quote }} 29 | {{ range $namespaces }} 30 | --- 31 | apiVersion: rbac.authorization.k8s.io/v1 32 | kind: RoleBinding 33 | metadata: 34 | name: {{ $fullname }}-{{ . }} 35 | namespace: {{ . }} 36 | labels: 37 | app.kubernetes.io/name: {{ $fullname }} 38 | app.kubernetes.io/instance: {{ $.Release.Name }} 39 | app.kubernetes.io/managed-by: Helm 40 | app.kubernetes.io/version: {{ $appVersion }} 41 | k8s-app: {{ $fullname }} 42 | helm.sh/chart: {{ $chartVersion }} 43 | roleRef: 44 | kind: Role 45 | apiGroup: rbac.authorization.k8s.io 46 | name: {{ $fullname }}-{{ . }} 47 | subjects: 48 | - kind: ServiceAccount 49 | name: {{ $serviceAccountName }} 50 | namespace: {{ $releaseNamespace }} 51 | {{ end }} 52 | {{ end }} -------------------------------------------------------------------------------- /templates/hooks/certificate/sdk_update_pre_build_request.go.tpl: -------------------------------------------------------------------------------- 1 | if delta.DifferentAt("Spec.Status.IssuedAt") { 2 | rlog.Info("Exporting certificate due to IssuedAt change") 3 | if err = rm.exportCertificate(ctx, &resource{latest.ko}); err != nil { 4 | rlog.Info("failed to export certificate", "error", err) 5 | return nil, err 6 | } else { 7 | rlog.Info("Certificate export completed successfully") 8 | } 9 | ko := desired.ko.DeepCopy() 10 | 11 | rm.setStatusDefaults(ko) 12 | ko.Status.IssuedAt = latest.ko.Status.IssuedAt 13 | ko.Status.Status = latest.ko.Status.Status 14 | ko.Status.Serial = latest.ko.Status.Serial 15 | return &resource{ko}, nil 16 | } 17 | 18 | if delta.DifferentAt("Spec.Status.Serial") { 19 | rlog.Info("Exporting certificate due to Serial change") 20 | if err = rm.exportCertificate(ctx, &resource{latest.ko}); err != nil { 21 | rlog.Info("failed to export certificate", "error", err) 22 | return nil, err 23 | } else { 24 | rlog.Info("Certificate export completed successfully") 25 | } 26 | ko := desired.ko.DeepCopy() 27 | 28 | rm.setStatusDefaults(ko) 29 | ko.Status.IssuedAt = latest.ko.Status.IssuedAt 30 | ko.Status.Status = latest.ko.Status.Status 31 | ko.Status.Serial = latest.ko.Status.Serial 32 | return &resource{ko}, nil 33 | } 34 | 35 | if delta.DifferentAt("Spec.Tags") { 36 | if err := syncTags( 37 | ctx, rm.sdkapi, rm.metrics, 38 | string(*desired.ko.Status.ACKResourceMetadata.ARN), 39 | desired.ko.Spec.Tags, latest.ko.Spec.Tags, 40 | ); err != nil { 41 | return nil, err 42 | } 43 | } 44 | if !delta.DifferentExcept("Spec.Tags") { 45 | return desired, nil 46 | } 47 | if latest.ko.Status.Type != nil && *latest.ko.Status.Type == string(svcapitypes.CertificateType_IMPORTED) { 48 | if delta.DifferentAt("Spec.Options") { 49 | return nil, ackerr.NewTerminalError(errors.New("only tags can be updated for an imported certificate")) 50 | } 51 | return desired, nil 52 | } 53 | -------------------------------------------------------------------------------- /helm/crds/services.k8s.aws_iamroleselectors.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.19.0 7 | name: iamroleselectors.services.k8s.aws 8 | spec: 9 | group: services.k8s.aws 10 | names: 11 | kind: IAMRoleSelector 12 | listKind: IAMRoleSelectorList 13 | plural: iamroleselectors 14 | singular: iamroleselector 15 | scope: Cluster 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: IAMRoleSelector is the schema for the IAMRoleSelector API. 21 | properties: 22 | apiVersion: 23 | description: |- 24 | APIVersion defines the versioned schema of this representation of an object. 25 | Servers should convert recognized schemas to the latest internal value, and 26 | may reject unrecognized values. 27 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 28 | type: string 29 | kind: 30 | description: |- 31 | Kind is a string value representing the REST resource this object represents. 32 | Servers may infer this from the endpoint the client submits requests to. 33 | Cannot be updated. 34 | In CamelCase. 35 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | properties: 41 | arn: 42 | type: string 43 | x-kubernetes-validations: 44 | - message: Value is immutable once set 45 | rule: self == oldSelf 46 | namespaceSelector: 47 | description: IAMRoleSelectorSpec defines the desired state of IAMRoleSelector 48 | properties: 49 | labelSelector: 50 | description: LabelSelector is a label query over a set of resources. 51 | properties: 52 | matchLabels: 53 | additionalProperties: 54 | type: string 55 | type: object 56 | required: 57 | - matchLabels 58 | type: object 59 | names: 60 | items: 61 | type: string 62 | type: array 63 | required: 64 | - names 65 | type: object 66 | resourceTypeSelector: 67 | items: 68 | properties: 69 | group: 70 | type: string 71 | kind: 72 | type: string 73 | version: 74 | type: string 75 | required: 76 | - group 77 | - kind 78 | - version 79 | type: object 80 | type: array 81 | required: 82 | - arn 83 | type: object 84 | status: 85 | type: object 86 | type: object 87 | served: true 88 | storage: true 89 | subresources: 90 | status: {} 91 | -------------------------------------------------------------------------------- /config/crd/common/bases/services.k8s.aws_iamroleselectors.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.19.0 7 | name: iamroleselectors.services.k8s.aws 8 | spec: 9 | group: services.k8s.aws 10 | names: 11 | kind: IAMRoleSelector 12 | listKind: IAMRoleSelectorList 13 | plural: iamroleselectors 14 | singular: iamroleselector 15 | scope: Cluster 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: IAMRoleSelector is the schema for the IAMRoleSelector API. 21 | properties: 22 | apiVersion: 23 | description: |- 24 | APIVersion defines the versioned schema of this representation of an object. 25 | Servers should convert recognized schemas to the latest internal value, and 26 | may reject unrecognized values. 27 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 28 | type: string 29 | kind: 30 | description: |- 31 | Kind is a string value representing the REST resource this object represents. 32 | Servers may infer this from the endpoint the client submits requests to. 33 | Cannot be updated. 34 | In CamelCase. 35 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | properties: 41 | arn: 42 | type: string 43 | x-kubernetes-validations: 44 | - message: Value is immutable once set 45 | rule: self == oldSelf 46 | namespaceSelector: 47 | description: IAMRoleSelectorSpec defines the desired state of IAMRoleSelector 48 | properties: 49 | labelSelector: 50 | description: LabelSelector is a label query over a set of resources. 51 | properties: 52 | matchLabels: 53 | additionalProperties: 54 | type: string 55 | type: object 56 | required: 57 | - matchLabels 58 | type: object 59 | names: 60 | items: 61 | type: string 62 | type: array 63 | required: 64 | - names 65 | type: object 66 | resourceTypeSelector: 67 | items: 68 | properties: 69 | group: 70 | type: string 71 | kind: 72 | type: string 73 | version: 74 | type: string 75 | required: 76 | - group 77 | - kind 78 | - version 79 | type: object 80 | type: array 81 | required: 82 | - arn 83 | type: object 84 | status: 85 | type: object 86 | type: object 87 | served: true 88 | storage: true 89 | subresources: 90 | status: {} 91 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug 4 | report, new feature, correction, or additional documentation, we greatly value 5 | feedback and contributions from our community. 6 | 7 | Please read through this document before submitting any issues or pull requests 8 | to ensure we have all the necessary information to effectively respond to your 9 | bug report or contribution. 10 | 11 | ## Reporting Bugs/Feature Requests 12 | 13 | We welcome you to use the GitHub issue tracker to report bugs or suggest 14 | features. 15 | 16 | When filing an issue, please check existing open, or recently closed, issues to 17 | make sure somebody else hasn't already reported the issue. Please try to 18 | include as much information as you can. Details like these are incredibly 19 | useful: 20 | 21 | * A reproducible test case or series of steps 22 | * The version of our code being used 23 | * Any modifications you've made relevant to the bug 24 | * Anything unusual about your environment or deployment 25 | 26 | ## Contributing via Pull Requests 27 | 28 | Contributions via pull requests are much appreciated. Before sending us a pull 29 | request, please ensure that: 30 | 31 | 1. You are working against the latest source on the *main* branch. 32 | 2. You check existing open, and recently merged, pull requests to make sure 33 | someone else hasn't addressed the problem already. 34 | 3. You open an issue to discuss any significant work - we would hate for your 35 | time to be wasted. 36 | 37 | To send us a pull request, please: 38 | 39 | 1. Fork the repository. 40 | 2. Modify the source; please focus on the specific change you are contributing. 41 | If you also reformat all the code, it will be hard for us to focus on your 42 | change. 43 | 3. Ensure local tests pass. 44 | 4. Commit to your fork using clear commit messages. 45 | 5. Send us a pull request, answering any default questions in the pull request 46 | interface. 47 | 6. Pay attention to any automated CI failures reported in the pull request, and 48 | stay involved in the conversation. 49 | 50 | GitHub provides additional document on [forking a repository][fork] and 51 | [creating a pull request][pr]. 52 | 53 | [fork]: https://help.github.com/articles/fork-a-repo/ 54 | [pr]: https://help.github.com/articles/creating-a-pull-request/ 55 | 56 | ## Finding contributions to work on 57 | 58 | Looking at the existing issues is a great way to find something to contribute 59 | on. As our projects, by default, use the default GitHub issue labels 60 | (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at 61 | any 'help wanted' issues is a great place to start. 62 | 63 | ## Developer documentation 64 | 65 | [See the documentation][dev-docs] for detailed development information. 66 | 67 | [dev-docs]: https://aws-controllers-k8s.github.io/community/docs/contributor-docs/overview/ 68 | 69 | ## Code of Conduct 70 | 71 | We adhere to the [Amazon Open Source Code of Conduct][coc]. 72 | 73 | [coc]: https://aws.github.io/code-of-conduct 74 | 75 | ## Security issue notifications 76 | 77 | If you discover a potential security issue in this project we ask that you 78 | notify AWS/Amazon Security via our [vulnerability reporting page][vuln]. Please 79 | do **not** create a public Github issue. 80 | 81 | [vuln]: http://aws.amazon.com/security/vulnerability-reporting/ 82 | 83 | ## License 84 | 85 | This project is [licensed][./LICENSE] under the Apache-2.0 License. 86 | -------------------------------------------------------------------------------- /pkg/resource/certificate/manager_factory.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package certificate 17 | 18 | import ( 19 | "fmt" 20 | "sync" 21 | 22 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 23 | ackcfg "github.com/aws-controllers-k8s/runtime/pkg/config" 24 | ackmetrics "github.com/aws-controllers-k8s/runtime/pkg/metrics" 25 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 26 | "github.com/aws/aws-sdk-go-v2/aws" 27 | "github.com/go-logr/logr" 28 | 29 | svcresource "github.com/aws-controllers-k8s/acm-controller/pkg/resource" 30 | ) 31 | 32 | // resourceManagerFactory produces resourceManager objects. It implements the 33 | // `types.AWSResourceManagerFactory` interface. 34 | type resourceManagerFactory struct { 35 | sync.RWMutex 36 | // rmCache contains resource managers for a particular AWS account ID 37 | rmCache map[string]*resourceManager 38 | } 39 | 40 | // ResourcePrototype returns an AWSResource that resource managers produced by 41 | // this factory will handle 42 | func (f *resourceManagerFactory) ResourceDescriptor() acktypes.AWSResourceDescriptor { 43 | return &resourceDescriptor{} 44 | } 45 | 46 | // ManagerFor returns a resource manager object that can manage resources for a 47 | // supplied AWS account 48 | func (f *resourceManagerFactory) ManagerFor( 49 | cfg ackcfg.Config, 50 | clientcfg aws.Config, 51 | log logr.Logger, 52 | metrics *ackmetrics.Metrics, 53 | rr acktypes.Reconciler, 54 | id ackv1alpha1.AWSAccountID, 55 | region ackv1alpha1.AWSRegion, 56 | roleARN ackv1alpha1.AWSResourceName, 57 | ) (acktypes.AWSResourceManager, error) { 58 | // We use the account ID, region, and role ARN to uniquely identify a 59 | // resource manager. This helps us to avoid creating multiple resource 60 | // managers for the same account/region/roleARN combination. 61 | rmId := fmt.Sprintf("%s/%s/%s", id, region, roleARN) 62 | f.RLock() 63 | rm, found := f.rmCache[rmId] 64 | f.RUnlock() 65 | 66 | if found { 67 | return rm, nil 68 | } 69 | 70 | f.Lock() 71 | defer f.Unlock() 72 | 73 | rm, err := newResourceManager(cfg, clientcfg, log, metrics, rr, id, region) 74 | if err != nil { 75 | return nil, err 76 | } 77 | f.rmCache[rmId] = rm 78 | return rm, nil 79 | } 80 | 81 | // IsAdoptable returns true if the resource is able to be adopted 82 | func (f *resourceManagerFactory) IsAdoptable() bool { 83 | return true 84 | } 85 | 86 | // RequeueOnSuccessSeconds returns true if the resource should be requeued after specified seconds 87 | // Default is false which means resource will not be requeued after success. 88 | func (f *resourceManagerFactory) RequeueOnSuccessSeconds() int { 89 | return 60 90 | } 91 | 92 | func newResourceManagerFactory() *resourceManagerFactory { 93 | return &resourceManagerFactory{ 94 | rmCache: map[string]*resourceManager{}, 95 | } 96 | } 97 | 98 | func init() { 99 | svcresource.RegisterManagerFactory(newResourceManagerFactory()) 100 | } 101 | -------------------------------------------------------------------------------- /config/controller/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: ack-system 5 | --- 6 | apiVersion: apps/v1 7 | kind: Deployment 8 | metadata: 9 | name: ack-acm-controller 10 | namespace: ack-system 11 | labels: 12 | app.kubernetes.io/name: ack-acm-controller 13 | app.kubernetes.io/part-of: ack-system 14 | spec: 15 | selector: 16 | matchLabels: 17 | app.kubernetes.io/name: ack-acm-controller 18 | replicas: 1 19 | template: 20 | metadata: 21 | labels: 22 | app.kubernetes.io/name: ack-acm-controller 23 | spec: 24 | containers: 25 | - command: 26 | - ./bin/controller 27 | args: 28 | - --aws-region 29 | - "$(AWS_REGION)" 30 | - --aws-endpoint-url 31 | - "$(AWS_ENDPOINT_URL)" 32 | - --enable-development-logging=$(ACK_ENABLE_DEVELOPMENT_LOGGING) 33 | - --log-level 34 | - "$(ACK_LOG_LEVEL)" 35 | - --resource-tags 36 | - "$(ACK_RESOURCE_TAGS)" 37 | - --watch-namespace 38 | - "$(ACK_WATCH_NAMESPACE)" 39 | - --enable-leader-election=$(ENABLE_LEADER_ELECTION) 40 | - --leader-election-namespace 41 | - "$(LEADER_ELECTION_NAMESPACE)" 42 | - --reconcile-default-max-concurrent-syncs 43 | - "$(RECONCILE_DEFAULT_MAX_CONCURRENT_SYNCS)" 44 | - --feature-gates 45 | - "$(FEATURE_GATES)" 46 | - --enable-carm=$(ENABLE_CARM) 47 | image: controller:latest 48 | name: controller 49 | ports: 50 | - name: http 51 | containerPort: 8080 52 | resources: 53 | limits: 54 | cpu: 100m 55 | memory: 300Mi 56 | requests: 57 | cpu: 100m 58 | memory: 200Mi 59 | env: 60 | - name: ACK_SYSTEM_NAMESPACE 61 | valueFrom: 62 | fieldRef: 63 | fieldPath: metadata.namespace 64 | - name: AWS_REGION 65 | value: "" 66 | - name: AWS_ENDPOINT_URL 67 | value: "" 68 | - name: ACK_WATCH_NAMESPACE 69 | value: "" 70 | - name: ACK_ENABLE_DEVELOPMENT_LOGGING 71 | value: "false" 72 | - name: ACK_LOG_LEVEL 73 | value: "info" 74 | - name: ACK_RESOURCE_TAGS 75 | value: "services.k8s.aws/controller-version=%CONTROLLER_SERVICE%-%CONTROLLER_VERSION%,services.k8s.aws/namespace=%K8S_NAMESPACE%" 76 | - name: ENABLE_LEADER_ELECTION 77 | value: "false" 78 | - name: LEADER_ELECTION_NAMESPACE 79 | value: "ack-system" 80 | - name: "RECONCILE_DEFAULT_MAX_CONCURRENT_SYNCS" 81 | value: "1" 82 | - name: "FEATURE_GATES" 83 | value: "" 84 | - name: "ENABLE_CARM" 85 | value: "true" 86 | securityContext: 87 | allowPrivilegeEscalation: false 88 | privileged: false 89 | runAsNonRoot: true 90 | capabilities: 91 | drop: 92 | - ALL 93 | livenessProbe: 94 | httpGet: 95 | path: /healthz 96 | port: 8081 97 | initialDelaySeconds: 15 98 | periodSeconds: 20 99 | readinessProbe: 100 | httpGet: 101 | path: /readyz 102 | port: 8081 103 | initialDelaySeconds: 5 104 | periodSeconds: 10 105 | securityContext: 106 | seccompProfile: 107 | type: RuntimeDefault 108 | terminationGracePeriodSeconds: 10 109 | serviceAccountName: ack-acm-controller 110 | hostIPC: false 111 | hostPID: false 112 | hostNetwork: false 113 | dnsPolicy: ClusterFirst 114 | -------------------------------------------------------------------------------- /helm/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* The name of the application this chart installs */}} 2 | {{- define "ack-acm-controller.app.name" -}} 3 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 4 | {{- end -}} 5 | 6 | {{/* 7 | Create a default fully qualified app name. 8 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 9 | If release name contains chart name it will be used as a full name. 10 | */}} 11 | {{- define "ack-acm-controller.app.fullname" -}} 12 | {{- if .Values.fullnameOverride -}} 13 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 14 | {{- else -}} 15 | {{- $name := default .Chart.Name .Values.nameOverride -}} 16 | {{- if contains $name .Release.Name -}} 17 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 18 | {{- else -}} 19 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 20 | {{- end -}} 21 | {{- end -}} 22 | {{- end -}} 23 | 24 | {{/* The name and version as used by the chart label */}} 25 | {{- define "ack-acm-controller.chart.name-version" -}} 26 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 27 | {{- end -}} 28 | 29 | {{/* The name of the service account to use */}} 30 | {{- define "ack-acm-controller.service-account.name" -}} 31 | {{ default "default" .Values.serviceAccount.name }} 32 | {{- end -}} 33 | 34 | {{- define "ack-acm-controller.watch-namespace" -}} 35 | {{- if eq .Values.installScope "namespace" -}} 36 | {{ .Values.watchNamespace | default .Release.Namespace }} 37 | {{- end -}} 38 | {{- end -}} 39 | 40 | {{/* The mount path for the shared credentials file */}} 41 | {{- define "ack-acm-controller.aws.credentials.secret_mount_path" -}} 42 | {{- "/var/run/secrets/aws" -}} 43 | {{- end -}} 44 | 45 | {{/* The path the shared credentials file is mounted */}} 46 | {{- define "ack-acm-controller.aws.credentials.path" -}} 47 | {{ $secret_mount_path := include "ack-acm-controller.aws.credentials.secret_mount_path" . }} 48 | {{- printf "%s/%s" $secret_mount_path .Values.aws.credentials.secretKey -}} 49 | {{- end -}} 50 | 51 | {{/* The rules a of ClusterRole or Role */}} 52 | {{- define "ack-acm-controller.rbac-rules" -}} 53 | rules: 54 | - apiGroups: 55 | - "" 56 | resources: 57 | - configmaps 58 | - secrets 59 | verbs: 60 | - get 61 | - list 62 | - patch 63 | - watch 64 | - apiGroups: 65 | - "" 66 | resources: 67 | - namespaces 68 | verbs: 69 | - get 70 | - list 71 | - watch 72 | - apiGroups: 73 | - acm.services.k8s.aws 74 | resources: 75 | - certificates 76 | verbs: 77 | - create 78 | - delete 79 | - get 80 | - list 81 | - patch 82 | - update 83 | - watch 84 | - apiGroups: 85 | - acm.services.k8s.aws 86 | resources: 87 | - certificates/status 88 | verbs: 89 | - get 90 | - patch 91 | - update 92 | - apiGroups: 93 | - acmpca.services.k8s.aws 94 | resources: 95 | - certificateauthorities 96 | - certificateauthorities/status 97 | verbs: 98 | - get 99 | - list 100 | - apiGroups: 101 | - services.k8s.aws 102 | resources: 103 | - fieldexports 104 | - iamroleselectors 105 | verbs: 106 | - create 107 | - delete 108 | - get 109 | - list 110 | - patch 111 | - update 112 | - watch 113 | - apiGroups: 114 | - services.k8s.aws 115 | resources: 116 | - fieldexports/status 117 | - iamroleselectors/status 118 | verbs: 119 | - get 120 | - patch 121 | - update 122 | {{- end }} 123 | 124 | {{/* Convert k/v map to string like: "key1=value1,key2=value2,..." */}} 125 | {{- define "ack-acm-controller.feature-gates" -}} 126 | {{- $list := list -}} 127 | {{- range $k, $v := .Values.featureGates -}} 128 | {{- $list = append $list (printf "%s=%s" $k ( $v | toString)) -}} 129 | {{- end -}} 130 | {{ join "," $list }} 131 | {{- end -}} 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACK service controller for AWS Certificate Manager 2 | 3 | This repository contains source code for the AWS Controllers for Kubernetes 4 | (ACK) service controller for ACM. 5 | 6 | Please [log issues][ack-issues] and feedback on the main AWS Controllers for 7 | Kubernetes Github project. 8 | 9 | [ack-issues]: https://github.com/aws/aws-controllers-k8s/issues 10 | 11 | ## Getting Started 12 | 13 | ### Installation Instructions 14 | Learn more about [installing ACK service controller for AWS Certificate Manager](https://docs.aws.amazon.com/acm/latest/userguide/exportable-certificates-kubernetes.html). 15 | 16 | ### Pricing 17 | The ACK service controller for AWS Certificate Manager is free of charge. If you issue an [exportable public certificate](https://docs.aws.amazon.com/acm/latest/userguide/acm-exportable-certificates.html) with AWS Certificate Manager, there is a charge at certificate issuance and again when the certificate renews. Learn more about [AWS Certificate Manager Pricing](https://aws.amazon.com/certificate-manager/pricing/). 18 | 19 | [samples]: https://github.com/aws-controllers-k8s/acmpca-controller/tree/main/samples 20 | 21 | ### Kubernetes Secrets 22 | The ACK service controller for AWS Certificate Manager uses Kubernetes TLS Secrets to store the certificate chain and decrypted private key of the exported ACM certificate. Users are expected to create Secrets before creating Certificate resources. As these resources are created, the Secrets' `tls.crt` will be injected with the base64-encoded certificate and `tls.key` will be injected with the base64-encoded private key associated with the certificate. Users are responsible for deleting Secrets. 23 | 24 | In addition, after a certificate is successfully renewed by ACM, the ACK service controller for AWS Certificate Manager will automatically export the renewed certificate again so that the Kubernetes TLS Secret `exportTo` contains the certificate data and private key data of the renewed certificate. 25 | 26 | 27 | #### Export Certificate 28 | To export an ACM certificate to a Kubernetes TLS Secret, users must specify the namespace and the name of the Secret using the `exportTo` field of the Certificate resource, as shown below. 29 | 30 | ##### Exporting an exportable ACM public certificate 31 | ``` 32 | apiVersion: v1 33 | kind: Secret 34 | type: kubernetes.io/tls 35 | metadata: 36 | name: exported-cert-secret 37 | namespace: demo-app 38 | data: 39 | tls.crt: "" 40 | tls.key: "" 41 | --- 42 | apiVersion: acm.services.k8s.aws/v1alpha1 43 | kind: Certificate 44 | metadata: 45 | name: exportable-public-cert 46 | namespace: demo-app 47 | spec: 48 | domainName: my.domain.com 49 | options: 50 | certificateTransparencyLoggingPreference: ENABLED 51 | exportTo: 52 | namespace: demo-app 53 | name: exported-cert-secret 54 | key: tls.crt 55 | ... 56 | ``` 57 | 58 | ##### Exporting an ACM private certificate 59 | ``` 60 | apiVersion: v1 61 | kind: Secret 62 | type: kubernetes.io/tls 63 | metadata: 64 | name: exported-cert-secret 65 | namespace: demo-app-2 66 | data: 67 | tls.crt: "" 68 | tls.key: "" 69 | --- 70 | apiVersion: acm.services.k8s.aws/v1alpha1 71 | kind: Certificate 72 | metadata: 73 | name: exportable-private-cert 74 | namespace: demo-app-2 75 | spec: 76 | domainName: my.domain.com 77 | certificateAuthorityARN: arn:aws:acm-pca:{$REGION}:{$AWS_ACCOUNT}:certificate-authority/12345678-1234-1234-1234-123456789012 78 | keyAlgorithm: EC_secp384r1 79 | exportTo: 80 | namespace: demo-app-2 81 | name: exported-cert-secret 82 | key: tls.crt 83 | ``` 84 | If you are issuing a privately trusted certificate, please also consider using this cert-manager plugin: https://github.com/cert-manager/aws-privateca-issuer/. 85 | 86 | ## Contributing 87 | 88 | We welcome community contributions and pull requests. 89 | 90 | See our [contribution guide](/CONTRIBUTING.md) for more information on how to 91 | report issues, set up a development environment, and submit code. 92 | 93 | We adhere to the [Amazon Open Source Code of Conduct][coc]. 94 | 95 | You can also learn more about our [Governance](/GOVERNANCE.md) structure. 96 | 97 | [coc]: https://aws.github.io/code-of-conduct 98 | 99 | ## License 100 | 101 | This project is [licensed](/LICENSE) under the Apache-2.0 License. 102 | -------------------------------------------------------------------------------- /pkg/resource/certificate/tags.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package certificate 17 | 18 | import ( 19 | "slices" 20 | "strings" 21 | 22 | acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" 23 | 24 | svcapitypes "github.com/aws-controllers-k8s/acm-controller/apis/v1alpha1" 25 | ) 26 | 27 | var ( 28 | _ = svcapitypes.Certificate{} 29 | _ = acktags.NewTags() 30 | ) 31 | 32 | // convertToOrderedACKTags converts the tags parameter into 'acktags.Tags' shape. 33 | // This method helps in creating the hub(acktags.Tags) for merging 34 | // default controller tags with existing resource tags. It also returns a slice 35 | // of keys maintaining the original key Order when the tags are a list 36 | func convertToOrderedACKTags(tags []*svcapitypes.Tag) (acktags.Tags, []string) { 37 | result := acktags.NewTags() 38 | keyOrder := []string{} 39 | 40 | if len(tags) == 0 { 41 | return result, keyOrder 42 | } 43 | for _, t := range tags { 44 | if t.Key != nil { 45 | keyOrder = append(keyOrder, *t.Key) 46 | if t.Value != nil { 47 | result[*t.Key] = *t.Value 48 | } else { 49 | result[*t.Key] = "" 50 | } 51 | } 52 | } 53 | 54 | return result, keyOrder 55 | } 56 | 57 | // fromACKTags converts the tags parameter into []*svcapitypes.Tag shape. 58 | // This method helps in setting the tags back inside AWSResource after merging 59 | // default controller tags with existing resource tags. When a list, 60 | // it maintains the order from original 61 | func fromACKTags(tags acktags.Tags, keyOrder []string) []*svcapitypes.Tag { 62 | result := []*svcapitypes.Tag{} 63 | 64 | for _, k := range keyOrder { 65 | v, ok := tags[k] 66 | if ok { 67 | tag := svcapitypes.Tag{Key: &k, Value: &v} 68 | result = append(result, &tag) 69 | delete(tags, k) 70 | } 71 | } 72 | for k, v := range tags { 73 | tag := svcapitypes.Tag{Key: &k, Value: &v} 74 | result = append(result, &tag) 75 | } 76 | 77 | return result 78 | } 79 | 80 | // ignoreSystemTags ignores tags that have keys that start with "aws:" 81 | // and systemTags defined on startup via the --resource-tags flag, 82 | // to avoid patching them to the resourceSpec. 83 | // Eg. resources created with cloudformation have tags that cannot be 84 | // removed by an ACK controller 85 | func ignoreSystemTags(tags acktags.Tags, systemTags []string) { 86 | for k := range tags { 87 | if strings.HasPrefix(k, "aws:") || 88 | slices.Contains(systemTags, k) { 89 | delete(tags, k) 90 | } 91 | } 92 | } 93 | 94 | // syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state 95 | // are preserved in the desired state. This prevents the controller from attempting to 96 | // modify AWS-managed tags, which would result in an error. 97 | // 98 | // AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) 99 | // and cannot be modified or deleted through normal tag operations. Common examples include: 100 | // - aws:cloudformation:stack-name 101 | // - aws:servicecatalog:productArn 102 | // 103 | // Parameters: 104 | // - a: The target Tags map to be updated (typically desired state) 105 | // - b: The source Tags map containing AWS-managed tags (typically latest state) 106 | // 107 | // Example: 108 | // 109 | // latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} 110 | // desired := Tags{"environment": "dev"} 111 | // SyncAWSTags(desired, latest) 112 | // desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} 113 | func syncAWSTags(a acktags.Tags, b acktags.Tags) { 114 | for k := range b { 115 | if strings.HasPrefix(k, "aws:") { 116 | a[k] = b[k] 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /pkg/resource/certificate/resource.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package certificate 17 | 18 | import ( 19 | "fmt" 20 | 21 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 22 | ackerrors "github.com/aws-controllers-k8s/runtime/pkg/errors" 23 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | rtclient "sigs.k8s.io/controller-runtime/pkg/client" 26 | 27 | svcapitypes "github.com/aws-controllers-k8s/acm-controller/apis/v1alpha1" 28 | ) 29 | 30 | // Hack to avoid import errors during build... 31 | var ( 32 | _ = &ackerrors.MissingNameIdentifier 33 | ) 34 | 35 | // resource implements the `aws-controller-k8s/runtime/pkg/types.AWSResource` 36 | // interface 37 | type resource struct { 38 | // The Kubernetes-native CR representing the resource 39 | ko *svcapitypes.Certificate 40 | } 41 | 42 | // Identifiers returns an AWSResourceIdentifiers object containing various 43 | // identifying information, including the AWS account ID that owns the 44 | // resource, the resource's AWS Resource Name (ARN) 45 | func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { 46 | return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} 47 | } 48 | 49 | // IsBeingDeleted returns true if the Kubernetes resource has a non-zero 50 | // deletion timestamp 51 | func (r *resource) IsBeingDeleted() bool { 52 | return !r.ko.DeletionTimestamp.IsZero() 53 | } 54 | 55 | // RuntimeObject returns the Kubernetes apimachinery/runtime representation of 56 | // the AWSResource 57 | func (r *resource) RuntimeObject() rtclient.Object { 58 | return r.ko 59 | } 60 | 61 | // MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object 62 | // representation of the AWSResource 63 | func (r *resource) MetaObject() metav1.Object { 64 | return r.ko.GetObjectMeta() 65 | } 66 | 67 | // Conditions returns the ACK Conditions collection for the AWSResource 68 | func (r *resource) Conditions() []*ackv1alpha1.Condition { 69 | return r.ko.Status.Conditions 70 | } 71 | 72 | // ReplaceConditions sets the Conditions status field for the resource 73 | func (r *resource) ReplaceConditions(conditions []*ackv1alpha1.Condition) { 74 | r.ko.Status.Conditions = conditions 75 | } 76 | 77 | // SetObjectMeta sets the ObjectMeta field for the resource 78 | func (r *resource) SetObjectMeta(meta metav1.ObjectMeta) { 79 | r.ko.ObjectMeta = meta 80 | } 81 | 82 | // SetStatus will set the Status field for the resource 83 | func (r *resource) SetStatus(desired acktypes.AWSResource) { 84 | r.ko.Status = desired.(*resource).ko.Status 85 | } 86 | 87 | // SetIdentifiers sets the Spec or Status field that is referenced as the unique 88 | // resource identifier 89 | func (r *resource) SetIdentifiers(identifier *ackv1alpha1.AWSIdentifiers) error { 90 | if r.ko.Status.ACKResourceMetadata == nil { 91 | r.ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{} 92 | } 93 | r.ko.Status.ACKResourceMetadata.ARN = identifier.ARN 94 | 95 | return nil 96 | } 97 | 98 | // PopulateResourceFromAnnotation populates the fields passed from adoption annotation 99 | func (r *resource) PopulateResourceFromAnnotation(fields map[string]string) error { 100 | resourceARN, ok := fields["arn"] 101 | if !ok { 102 | return ackerrors.NewTerminalError(fmt.Errorf("required field missing: arn")) 103 | } 104 | 105 | if r.ko.Status.ACKResourceMetadata == nil { 106 | r.ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{} 107 | } 108 | arn := ackv1alpha1.AWSResourceName(resourceARN) 109 | r.ko.Status.ACKResourceMetadata.ARN = &arn 110 | 111 | return nil 112 | } 113 | 114 | // DeepCopy will return a copy of the resource 115 | func (r *resource) DeepCopy() acktypes.AWSResource { 116 | koCopy := r.ko.DeepCopy() 117 | return &resource{koCopy} 118 | } 119 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aws-controllers-k8s/acm-controller 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/aws-controllers-k8s/acmpca-controller v0.0.17 9 | github.com/aws-controllers-k8s/runtime v0.56.0 10 | github.com/aws/aws-sdk-go v1.49.6 11 | github.com/aws/aws-sdk-go-v2 v1.39.2 12 | github.com/aws/aws-sdk-go-v2/service/acm v1.33.0 13 | github.com/aws/smithy-go v1.23.0 14 | github.com/go-logr/logr v1.4.2 15 | github.com/spf13/pflag v1.0.5 16 | github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 17 | k8s.io/api v0.32.1 18 | k8s.io/apimachinery v0.32.1 19 | k8s.io/client-go v0.32.1 20 | sigs.k8s.io/controller-runtime v0.20.4 21 | ) 22 | 23 | require ( 24 | github.com/aws/aws-sdk-go-v2/config v1.28.6 // indirect 25 | github.com/aws/aws-sdk-go-v2/credentials v1.17.47 // indirect 26 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 // indirect 27 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 // indirect 28 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 // indirect 29 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect 30 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect 31 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 // indirect 32 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect 33 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect 34 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 // indirect 35 | github.com/beorn7/perks v1.0.1 // indirect 36 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 37 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 38 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 39 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 40 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 41 | github.com/fsnotify/fsnotify v1.7.0 // indirect 42 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 43 | github.com/go-logr/zapr v1.3.0 // indirect 44 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 45 | github.com/go-openapi/jsonreference v0.20.2 // indirect 46 | github.com/go-openapi/swag v0.23.0 // indirect 47 | github.com/gogo/protobuf v1.3.2 // indirect 48 | github.com/golang/protobuf v1.5.4 // indirect 49 | github.com/google/btree v1.1.3 // indirect 50 | github.com/google/gnostic-models v0.6.8 // indirect 51 | github.com/google/go-cmp v0.6.0 // indirect 52 | github.com/google/gofuzz v1.2.0 // indirect 53 | github.com/google/uuid v1.6.0 // indirect 54 | github.com/itchyny/gojq v0.12.6 // indirect 55 | github.com/itchyny/timefmt-go v0.1.3 // indirect 56 | github.com/jaypipes/envutil v1.0.0 // indirect 57 | github.com/josharian/intern v1.0.0 // indirect 58 | github.com/json-iterator/go v1.1.12 // indirect 59 | github.com/mailru/easyjson v0.7.7 // indirect 60 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 61 | github.com/modern-go/reflect2 v1.0.2 // indirect 62 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 63 | github.com/pkg/errors v0.9.1 // indirect 64 | github.com/prometheus/client_golang v1.19.1 // indirect 65 | github.com/prometheus/client_model v0.6.1 // indirect 66 | github.com/prometheus/common v0.55.0 // indirect 67 | github.com/prometheus/procfs v0.15.1 // indirect 68 | github.com/samber/lo v1.37.0 // indirect 69 | github.com/x448/float16 v0.8.4 // indirect 70 | go.uber.org/multierr v1.11.0 // indirect 71 | go.uber.org/zap v1.27.0 // indirect 72 | golang.org/x/crypto v0.36.0 // indirect 73 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 74 | golang.org/x/net v0.38.0 // indirect 75 | golang.org/x/oauth2 v0.27.0 // indirect 76 | golang.org/x/sync v0.12.0 // indirect 77 | golang.org/x/sys v0.31.0 // indirect 78 | golang.org/x/term v0.30.0 // indirect 79 | golang.org/x/text v0.23.0 // indirect 80 | golang.org/x/time v0.7.0 // indirect 81 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 82 | google.golang.org/protobuf v1.35.1 // indirect 83 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 84 | gopkg.in/inf.v0 v0.9.1 // indirect 85 | gopkg.in/yaml.v3 v3.0.1 // indirect 86 | k8s.io/apiextensions-apiserver v0.32.1 // indirect 87 | k8s.io/klog/v2 v2.130.1 // indirect 88 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect 89 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 90 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 91 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect 92 | sigs.k8s.io/yaml v1.4.0 // indirect 93 | ) 94 | -------------------------------------------------------------------------------- /test/e2e/certificate.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | """Utilities for working with Certificate resources""" 15 | 16 | import datetime 17 | import time 18 | import typing 19 | 20 | import boto3 21 | import pytest 22 | 23 | DEFAULT_WAIT_UNTIL_TIMEOUT_SECONDS = 60*30 24 | DEFAULT_WAIT_UNTIL_INTERVAL_SECONDS = 15 25 | DEFAULT_WAIT_UNTIL_DELETED_TIMEOUT_SECONDS = 60*10 26 | DEFAULT_WAIT_UNTIL_DELETED_INTERVAL_SECONDS = 15 27 | 28 | CertificateMatchFunc = typing.NewType( 29 | 'CertificateMatchFunc', 30 | typing.Callable[[dict], bool], 31 | ) 32 | 33 | class StatusMatcher: 34 | def __init__(self, status): 35 | self.match_on = status 36 | 37 | def __call__(self, record: dict) -> bool: 38 | return ('Status' in record 39 | and record['Status'] == self.match_on) 40 | 41 | 42 | def status_matches(status: str) -> CertificateMatchFunc: 43 | return StatusMatcher(status) 44 | 45 | 46 | def wait_until( 47 | certificate_arn: str, 48 | match_fn: CertificateMatchFunc, 49 | timeout_seconds: int = DEFAULT_WAIT_UNTIL_TIMEOUT_SECONDS, 50 | interval_seconds: int = DEFAULT_WAIT_UNTIL_INTERVAL_SECONDS, 51 | ) -> None: 52 | """Waits until a Certificate with a supplied ARN is returned from the ACM 53 | API and the matching functor returns True. 54 | 55 | Usage: 56 | from e2e.certificate import wait_until, status_matches 57 | 58 | wait_until( 59 | certificate_arn, 60 | status_matches("ISSUED"), 61 | ) 62 | 63 | Raises: 64 | pytest.fail upon timeout 65 | """ 66 | now = datetime.datetime.now() 67 | timeout = now + datetime.timedelta(seconds=timeout_seconds) 68 | 69 | while not match_fn(get(certificate_arn)): 70 | if datetime.datetime.now() >= timeout: 71 | pytest.fail("failed to match Certificate before timeout") 72 | time.sleep(interval_seconds) 73 | 74 | 75 | def wait_until_deleted( 76 | certificate_arn: str, 77 | timeout_seconds: int = DEFAULT_WAIT_UNTIL_DELETED_TIMEOUT_SECONDS, 78 | interval_seconds: int = DEFAULT_WAIT_UNTIL_DELETED_INTERVAL_SECONDS, 79 | ) -> None: 80 | """Waits until a Certificate with a supplied ID is no longer returned from 81 | the ACM API. 82 | 83 | Usage: 84 | from e2e.db_instance import wait_until_deleted 85 | 86 | wait_until_deleted(instance_id) 87 | 88 | Raises: 89 | pytest.fail upon timeout or if the Certificate goes to any other status 90 | other than 'deleting' 91 | """ 92 | now = datetime.datetime.now() 93 | timeout = now + datetime.timedelta(seconds=timeout_seconds) 94 | 95 | while True: 96 | if datetime.datetime.now() >= timeout: 97 | pytest.fail( 98 | "Timed out waiting for Certificate to be " 99 | "deleted in ACM API" 100 | ) 101 | time.sleep(interval_seconds) 102 | 103 | latest = get(certificate_arn) 104 | if latest is None: 105 | break 106 | 107 | 108 | def get(certificate_arn): 109 | """Returns a dict containing the Certificate record from the ACM API. 110 | 111 | If no such Certificate exists, returns None. 112 | """ 113 | c = boto3.client('acm') 114 | try: 115 | resp = c.describe_certificate(CertificateArn=certificate_arn) 116 | return resp['Certificate'] 117 | except c.exceptions.ResourceNotFoundException: 118 | return None 119 | 120 | 121 | def get_tags(certificate_arn): 122 | """Returns a dict containing the Certificate's tag records from the ACM 123 | API. 124 | 125 | If no such Certificate exists, returns None. 126 | """ 127 | c = boto3.client('acm') 128 | try: 129 | resp = c.list_tags_for_certificate( 130 | CertificateArn=certificate_arn, 131 | ) 132 | return resp['Tags'] 133 | except c.exceptions.ResourceNotFoundException: 134 | return None 135 | -------------------------------------------------------------------------------- /pkg/resource/certificate/delta.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package certificate 17 | 18 | import ( 19 | "bytes" 20 | "reflect" 21 | 22 | ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" 23 | acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" 24 | ) 25 | 26 | // Hack to avoid import errors during build... 27 | var ( 28 | _ = &bytes.Buffer{} 29 | _ = &reflect.Method{} 30 | _ = &acktags.Tags{} 31 | ) 32 | 33 | // newResourceDelta returns a new `ackcompare.Delta` used to compare two 34 | // resources 35 | func newResourceDelta( 36 | a *resource, 37 | b *resource, 38 | ) *ackcompare.Delta { 39 | delta := ackcompare.NewDelta() 40 | if (a == nil && b != nil) || 41 | (a != nil && b == nil) { 42 | delta.Add("", a, b) 43 | return delta 44 | } 45 | compareCertificateIssuedAt(delta, a, b) 46 | 47 | if ackcompare.HasNilDifference(a.ko.Spec.CertificateARN, b.ko.Spec.CertificateARN) { 48 | delta.Add("Spec.CertificateARN", a.ko.Spec.CertificateARN, b.ko.Spec.CertificateARN) 49 | } else if a.ko.Spec.CertificateARN != nil && b.ko.Spec.CertificateARN != nil { 50 | if *a.ko.Spec.CertificateARN != *b.ko.Spec.CertificateARN { 51 | delta.Add("Spec.CertificateARN", a.ko.Spec.CertificateARN, b.ko.Spec.CertificateARN) 52 | } 53 | } 54 | if ackcompare.HasNilDifference(a.ko.Spec.CertificateAuthorityARN, b.ko.Spec.CertificateAuthorityARN) { 55 | delta.Add("Spec.CertificateAuthorityARN", a.ko.Spec.CertificateAuthorityARN, b.ko.Spec.CertificateAuthorityARN) 56 | } else if a.ko.Spec.CertificateAuthorityARN != nil && b.ko.Spec.CertificateAuthorityARN != nil { 57 | if *a.ko.Spec.CertificateAuthorityARN != *b.ko.Spec.CertificateAuthorityARN { 58 | delta.Add("Spec.CertificateAuthorityARN", a.ko.Spec.CertificateAuthorityARN, b.ko.Spec.CertificateAuthorityARN) 59 | } 60 | } 61 | if !reflect.DeepEqual(a.ko.Spec.CertificateAuthorityRef, b.ko.Spec.CertificateAuthorityRef) { 62 | delta.Add("Spec.CertificateAuthorityRef", a.ko.Spec.CertificateAuthorityRef, b.ko.Spec.CertificateAuthorityRef) 63 | } 64 | if ackcompare.HasNilDifference(a.ko.Spec.DomainName, b.ko.Spec.DomainName) { 65 | delta.Add("Spec.DomainName", a.ko.Spec.DomainName, b.ko.Spec.DomainName) 66 | } else if a.ko.Spec.DomainName != nil && b.ko.Spec.DomainName != nil { 67 | if *a.ko.Spec.DomainName != *b.ko.Spec.DomainName { 68 | delta.Add("Spec.DomainName", a.ko.Spec.DomainName, b.ko.Spec.DomainName) 69 | } 70 | } 71 | if ackcompare.HasNilDifference(a.ko.Spec.Options, b.ko.Spec.Options) { 72 | delta.Add("Spec.Options", a.ko.Spec.Options, b.ko.Spec.Options) 73 | } else if a.ko.Spec.Options != nil && b.ko.Spec.Options != nil { 74 | if ackcompare.HasNilDifference(a.ko.Spec.Options.CertificateTransparencyLoggingPreference, b.ko.Spec.Options.CertificateTransparencyLoggingPreference) { 75 | delta.Add("Spec.Options.CertificateTransparencyLoggingPreference", a.ko.Spec.Options.CertificateTransparencyLoggingPreference, b.ko.Spec.Options.CertificateTransparencyLoggingPreference) 76 | } else if a.ko.Spec.Options.CertificateTransparencyLoggingPreference != nil && b.ko.Spec.Options.CertificateTransparencyLoggingPreference != nil { 77 | if *a.ko.Spec.Options.CertificateTransparencyLoggingPreference != *b.ko.Spec.Options.CertificateTransparencyLoggingPreference { 78 | delta.Add("Spec.Options.CertificateTransparencyLoggingPreference", a.ko.Spec.Options.CertificateTransparencyLoggingPreference, b.ko.Spec.Options.CertificateTransparencyLoggingPreference) 79 | } 80 | } 81 | } 82 | if len(a.ko.Spec.SubjectAlternativeNames) != len(b.ko.Spec.SubjectAlternativeNames) { 83 | delta.Add("Spec.SubjectAlternativeNames", a.ko.Spec.SubjectAlternativeNames, b.ko.Spec.SubjectAlternativeNames) 84 | } else if len(a.ko.Spec.SubjectAlternativeNames) > 0 { 85 | if !ackcompare.SliceStringPEqual(a.ko.Spec.SubjectAlternativeNames, b.ko.Spec.SubjectAlternativeNames) { 86 | delta.Add("Spec.SubjectAlternativeNames", a.ko.Spec.SubjectAlternativeNames, b.ko.Spec.SubjectAlternativeNames) 87 | } 88 | } 89 | desiredACKTags, _ := convertToOrderedACKTags(a.ko.Spec.Tags) 90 | latestACKTags, _ := convertToOrderedACKTags(b.ko.Spec.Tags) 91 | if !ackcompare.MapStringStringEqual(desiredACKTags, latestACKTags) { 92 | delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags) 93 | } 94 | 95 | return delta 96 | } 97 | -------------------------------------------------------------------------------- /test/e2e/condition.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | """Utility functions to help processing Kubernetes resource conditions""" 15 | 16 | # TODO(jaypipes): Move these functions to acktest library. The reason these are 17 | # here is because the existing k8s.assert_condition_state_message doesn't 18 | # actually assert anything. It returns true or false and logs messages. 19 | 20 | import pytest 21 | 22 | from acktest.k8s import resource as k8s 23 | 24 | CONDITION_TYPE_ADOPTED = "ACK.Adopted" 25 | CONDITION_TYPE_RESOURCE_SYNCED = "ACK.ResourceSynced" 26 | CONDITION_TYPE_TERMINAL = "ACK.Terminal" 27 | CONDITION_TYPE_RECOVERABLE = "ACK.Recoverable" 28 | CONDITION_TYPE_ADVISORY = "ACK.Advisory" 29 | CONDITION_TYPE_LATE_INITIALIZED = "ACK.LateInitialized" 30 | 31 | 32 | def assert_type_status( 33 | ref: k8s.CustomResourceReference, 34 | cond_type_match: str = CONDITION_TYPE_RESOURCE_SYNCED, 35 | cond_status_match: bool = True, 36 | ): 37 | """Asserts that the supplied resource has a condition of type 38 | ACK.ResourceSynced and that the Status of this condition is True. 39 | 40 | Usage: 41 | from acktest.k8s import resource as k8s 42 | 43 | from e2e import condition 44 | 45 | ref = k8s.CustomResourceReference( 46 | CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, 47 | db_cluster_id, namespace="default", 48 | ) 49 | k8s.create_custom_resource(ref, resource_data) 50 | k8s.wait_resource_consumed_by_controller(ref) 51 | condition.assert_type_status( 52 | ref, 53 | condition.CONDITION_TYPE_RESOURCE_SYNCED, 54 | False) 55 | 56 | Raises: 57 | pytest.fail when condition of the specified type is not found or is not 58 | in the supplied status. 59 | """ 60 | cond = k8s.get_resource_condition(ref, cond_type_match) 61 | if cond is None: 62 | msg = (f"Failed to find {cond_type_match} condition in " 63 | f"resource {ref}") 64 | pytest.fail(msg) 65 | 66 | cond_status = cond.get('status', None) 67 | if str(cond_status) != str(cond_status_match): 68 | msg = (f"Expected {cond_type_match} condition to " 69 | f"have status {cond_status_match} but found {cond_status}") 70 | pytest.fail(msg) 71 | 72 | 73 | def assert_synced_status( 74 | ref: k8s.CustomResourceReference, 75 | cond_status_match: bool, 76 | ): 77 | """Asserts that the supplied resource has a condition of type 78 | ACK.ResourceSynced and that the Status of this condition is True. 79 | 80 | Usage: 81 | from acktest.k8s import resource as k8s 82 | 83 | from e2e import condition 84 | 85 | ref = k8s.CustomResourceReference( 86 | CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, 87 | db_cluster_id, namespace="default", 88 | ) 89 | k8s.create_custom_resource(ref, resource_data) 90 | k8s.wait_resource_consumed_by_controller(ref) 91 | condition.assert_synced_status(ref, False) 92 | 93 | Raises: 94 | pytest.fail when ACK.ResourceSynced condition is not found or is not in 95 | a True status. 96 | """ 97 | assert_type_status(ref, CONDITION_TYPE_RESOURCE_SYNCED, cond_status_match) 98 | 99 | 100 | def assert_synced(ref: k8s.CustomResourceReference): 101 | """Asserts that the supplied resource has a condition of type 102 | ACK.ResourceSynced and that the Status of this condition is True. 103 | 104 | Usage: 105 | from acktest.k8s import resource as k8s 106 | 107 | from e2e import condition 108 | 109 | ref = k8s.CustomResourceReference( 110 | CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, 111 | db_cluster_id, namespace="default", 112 | ) 113 | k8s.create_custom_resource(ref, resource_data) 114 | k8s.wait_resource_consumed_by_controller(ref) 115 | condition.assert_synced(ref) 116 | 117 | Raises: 118 | pytest.fail when ACK.ResourceSynced condition is not found or is not in 119 | a True status. 120 | """ 121 | return assert_synced_status(ref, True) 122 | 123 | 124 | def assert_not_synced(ref: k8s.CustomResourceReference): 125 | """Asserts that the supplied resource has a condition of type 126 | ACK.ResourceSynced and that the Status of this condition is False. 127 | 128 | Usage: 129 | from acktest.k8s import resource as k8s 130 | 131 | from e2e import condition 132 | 133 | ref = k8s.CustomResourceReference( 134 | CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, 135 | db_cluster_id, namespace="default", 136 | ) 137 | k8s.create_custom_resource(ref, resource_data) 138 | k8s.wait_resource_consumed_by_controller(ref) 139 | condition.assert_not_synced(ref) 140 | 141 | Raises: 142 | pytest.fail when ACK.ResourceSynced condition is not found or is not in 143 | a False status. 144 | """ 145 | return assert_synced_status(ref, False) 146 | -------------------------------------------------------------------------------- /pkg/resource/certificate/descriptor.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package certificate 17 | 18 | import ( 19 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 20 | ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" 21 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | rtclient "sigs.k8s.io/controller-runtime/pkg/client" 25 | k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 26 | 27 | svcapitypes "github.com/aws-controllers-k8s/acm-controller/apis/v1alpha1" 28 | ) 29 | 30 | const ( 31 | FinalizerString = "finalizers.acm.services.k8s.aws/Certificate" 32 | ) 33 | 34 | var ( 35 | GroupVersionResource = svcapitypes.GroupVersion.WithResource("certificates") 36 | GroupKind = metav1.GroupKind{ 37 | Group: "acm.services.k8s.aws", 38 | Kind: "Certificate", 39 | } 40 | ) 41 | 42 | // resourceDescriptor implements the 43 | // `aws-service-operator-k8s/pkg/types.AWSResourceDescriptor` interface 44 | type resourceDescriptor struct { 45 | } 46 | 47 | // GroupVersionKind returns a Kubernetes schema.GroupVersionKind struct that 48 | // describes the API Group, Version and Kind of CRs described by the descriptor 49 | func (d *resourceDescriptor) GroupVersionKind() schema.GroupVersionKind { 50 | return svcapitypes.GroupVersion.WithKind(GroupKind.Kind) 51 | } 52 | 53 | // EmptyRuntimeObject returns an empty object prototype that may be used in 54 | // apimachinery and k8s client operations 55 | func (d *resourceDescriptor) EmptyRuntimeObject() rtclient.Object { 56 | return &svcapitypes.Certificate{} 57 | } 58 | 59 | // ResourceFromRuntimeObject returns an AWSResource that has been initialized 60 | // with the supplied runtime.Object 61 | func (d *resourceDescriptor) ResourceFromRuntimeObject( 62 | obj rtclient.Object, 63 | ) acktypes.AWSResource { 64 | return &resource{ 65 | ko: obj.(*svcapitypes.Certificate), 66 | } 67 | } 68 | 69 | // Delta returns an `ackcompare.Delta` object containing the difference between 70 | // one `AWSResource` and another. 71 | func (d *resourceDescriptor) Delta(a, b acktypes.AWSResource) *ackcompare.Delta { 72 | return newResourceDelta(a.(*resource), b.(*resource)) 73 | } 74 | 75 | // IsManaged returns true if the supplied AWSResource is under the management 76 | // of an ACK service controller. What this means in practice is that the 77 | // underlying custom resource (CR) in the AWSResource has had a 78 | // resource-specific finalizer associated with it. 79 | func (d *resourceDescriptor) IsManaged( 80 | res acktypes.AWSResource, 81 | ) bool { 82 | obj := res.RuntimeObject() 83 | if obj == nil { 84 | // Should not happen. If it does, there is a bug in the code 85 | panic("nil RuntimeMetaObject in AWSResource") 86 | } 87 | // Remove use of custom code once 88 | // https://github.com/kubernetes-sigs/controller-runtime/issues/994 is 89 | // fixed. This should be able to be: 90 | // 91 | // return k8sctrlutil.ContainsFinalizer(obj, FinalizerString) 92 | return containsFinalizer(obj, FinalizerString) 93 | } 94 | 95 | // Remove once https://github.com/kubernetes-sigs/controller-runtime/issues/994 96 | // is fixed. 97 | func containsFinalizer(obj rtclient.Object, finalizer string) bool { 98 | f := obj.GetFinalizers() 99 | for _, e := range f { 100 | if e == finalizer { 101 | return true 102 | } 103 | } 104 | return false 105 | } 106 | 107 | // MarkManaged places the supplied resource under the management of ACK. What 108 | // this typically means is that the resource manager will decorate the 109 | // underlying custom resource (CR) with a finalizer that indicates ACK is 110 | // managing the resource and the underlying CR may not be deleted until ACK is 111 | // finished cleaning up any backend AWS service resources associated with the 112 | // CR. 113 | func (d *resourceDescriptor) MarkManaged( 114 | res acktypes.AWSResource, 115 | ) { 116 | obj := res.RuntimeObject() 117 | if obj == nil { 118 | // Should not happen. If it does, there is a bug in the code 119 | panic("nil RuntimeMetaObject in AWSResource") 120 | } 121 | k8sctrlutil.AddFinalizer(obj, FinalizerString) 122 | } 123 | 124 | // MarkUnmanaged removes the supplied resource from management by ACK. What 125 | // this typically means is that the resource manager will remove a finalizer 126 | // underlying custom resource (CR) that indicates ACK is managing the resource. 127 | // This will allow the Kubernetes API server to delete the underlying CR. 128 | func (d *resourceDescriptor) MarkUnmanaged( 129 | res acktypes.AWSResource, 130 | ) { 131 | obj := res.RuntimeObject() 132 | if obj == nil { 133 | // Should not happen. If it does, there is a bug in the code 134 | panic("nil RuntimeMetaObject in AWSResource") 135 | } 136 | k8sctrlutil.RemoveFinalizer(obj, FinalizerString) 137 | } 138 | 139 | // MarkAdopted places descriptors on the custom resource that indicate the 140 | // resource was not created from within ACK. 141 | func (d *resourceDescriptor) MarkAdopted( 142 | res acktypes.AWSResource, 143 | ) { 144 | obj := res.RuntimeObject() 145 | if obj == nil { 146 | // Should not happen. If it does, there is a bug in the code 147 | panic("nil RuntimeObject in AWSResource") 148 | } 149 | curr := obj.GetAnnotations() 150 | if curr == nil { 151 | curr = make(map[string]string) 152 | } 153 | curr[ackv1alpha1.AnnotationAdopted] = "true" 154 | obj.SetAnnotations(curr) 155 | } 156 | -------------------------------------------------------------------------------- /helm/crds/services.k8s.aws_fieldexports.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.19.0 7 | name: fieldexports.services.k8s.aws 8 | spec: 9 | group: services.k8s.aws 10 | names: 11 | kind: FieldExport 12 | listKind: FieldExportList 13 | plural: fieldexports 14 | singular: fieldexport 15 | scope: Namespaced 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: FieldExport is the schema for the FieldExport API. 21 | properties: 22 | apiVersion: 23 | description: |- 24 | APIVersion defines the versioned schema of this representation of an object. 25 | Servers should convert recognized schemas to the latest internal value, and 26 | may reject unrecognized values. 27 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 28 | type: string 29 | kind: 30 | description: |- 31 | Kind is a string value representing the REST resource this object represents. 32 | Servers may infer this from the endpoint the client submits requests to. 33 | Cannot be updated. 34 | In CamelCase. 35 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | description: FieldExportSpec defines the desired state of the FieldExport. 41 | properties: 42 | from: 43 | description: |- 44 | ResourceFieldSelector provides the values necessary to identify an individual 45 | field on an individual K8s resource. 46 | properties: 47 | path: 48 | type: string 49 | resource: 50 | description: |- 51 | NamespacedResource provides all the values necessary to identify an ACK 52 | resource of a given type (within the same namespace as the custom resource 53 | containing this type). 54 | properties: 55 | group: 56 | type: string 57 | kind: 58 | type: string 59 | name: 60 | type: string 61 | required: 62 | - group 63 | - kind 64 | - name 65 | type: object 66 | required: 67 | - path 68 | - resource 69 | type: object 70 | to: 71 | description: |- 72 | FieldExportTarget provides the values necessary to identify the 73 | output path for a field export. 74 | properties: 75 | key: 76 | description: Key overrides the default value (`.`) 77 | for the FieldExport target 78 | type: string 79 | kind: 80 | description: |- 81 | FieldExportOutputType represents all types that can be produced by a field 82 | export operation 83 | enum: 84 | - configmap 85 | - secret 86 | type: string 87 | name: 88 | type: string 89 | namespace: 90 | description: Namespace is marked as optional, so we cannot compose 91 | `NamespacedName` 92 | type: string 93 | required: 94 | - kind 95 | - name 96 | type: object 97 | required: 98 | - from 99 | - to 100 | type: object 101 | status: 102 | description: FieldExportStatus defines the observed status of the FieldExport. 103 | properties: 104 | conditions: 105 | description: |- 106 | A collection of `ackv1alpha1.Condition` objects that describe the various 107 | recoverable states of the field CR 108 | items: 109 | description: |- 110 | Condition is the common struct used by all CRDs managed by ACK service 111 | controllers to indicate terminal states of the CR and its backend AWS 112 | service API resource 113 | properties: 114 | lastTransitionTime: 115 | description: Last time the condition transitioned from one status 116 | to another. 117 | format: date-time 118 | type: string 119 | message: 120 | description: A human readable message indicating details about 121 | the transition. 122 | type: string 123 | reason: 124 | description: The reason for the condition's last transition. 125 | type: string 126 | status: 127 | description: Status of the condition, one of True, False, Unknown. 128 | type: string 129 | type: 130 | description: Type is the type of the Condition 131 | type: string 132 | required: 133 | - status 134 | - type 135 | type: object 136 | type: array 137 | required: 138 | - conditions 139 | type: object 140 | type: object 141 | served: true 142 | storage: true 143 | subresources: 144 | status: {} 145 | -------------------------------------------------------------------------------- /config/crd/common/bases/services.k8s.aws_fieldexports.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.19.0 7 | name: fieldexports.services.k8s.aws 8 | spec: 9 | group: services.k8s.aws 10 | names: 11 | kind: FieldExport 12 | listKind: FieldExportList 13 | plural: fieldexports 14 | singular: fieldexport 15 | scope: Namespaced 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: FieldExport is the schema for the FieldExport API. 21 | properties: 22 | apiVersion: 23 | description: |- 24 | APIVersion defines the versioned schema of this representation of an object. 25 | Servers should convert recognized schemas to the latest internal value, and 26 | may reject unrecognized values. 27 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 28 | type: string 29 | kind: 30 | description: |- 31 | Kind is a string value representing the REST resource this object represents. 32 | Servers may infer this from the endpoint the client submits requests to. 33 | Cannot be updated. 34 | In CamelCase. 35 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | description: FieldExportSpec defines the desired state of the FieldExport. 41 | properties: 42 | from: 43 | description: |- 44 | ResourceFieldSelector provides the values necessary to identify an individual 45 | field on an individual K8s resource. 46 | properties: 47 | path: 48 | type: string 49 | resource: 50 | description: |- 51 | NamespacedResource provides all the values necessary to identify an ACK 52 | resource of a given type (within the same namespace as the custom resource 53 | containing this type). 54 | properties: 55 | group: 56 | type: string 57 | kind: 58 | type: string 59 | name: 60 | type: string 61 | required: 62 | - group 63 | - kind 64 | - name 65 | type: object 66 | required: 67 | - path 68 | - resource 69 | type: object 70 | to: 71 | description: |- 72 | FieldExportTarget provides the values necessary to identify the 73 | output path for a field export. 74 | properties: 75 | key: 76 | description: Key overrides the default value (`.`) 77 | for the FieldExport target 78 | type: string 79 | kind: 80 | description: |- 81 | FieldExportOutputType represents all types that can be produced by a field 82 | export operation 83 | enum: 84 | - configmap 85 | - secret 86 | type: string 87 | name: 88 | type: string 89 | namespace: 90 | description: Namespace is marked as optional, so we cannot compose 91 | `NamespacedName` 92 | type: string 93 | required: 94 | - kind 95 | - name 96 | type: object 97 | required: 98 | - from 99 | - to 100 | type: object 101 | status: 102 | description: FieldExportStatus defines the observed status of the FieldExport. 103 | properties: 104 | conditions: 105 | description: |- 106 | A collection of `ackv1alpha1.Condition` objects that describe the various 107 | recoverable states of the field CR 108 | items: 109 | description: |- 110 | Condition is the common struct used by all CRDs managed by ACK service 111 | controllers to indicate terminal states of the CR and its backend AWS 112 | service API resource 113 | properties: 114 | lastTransitionTime: 115 | description: Last time the condition transitioned from one status 116 | to another. 117 | format: date-time 118 | type: string 119 | message: 120 | description: A human readable message indicating details about 121 | the transition. 122 | type: string 123 | reason: 124 | description: The reason for the condition's last transition. 125 | type: string 126 | status: 127 | description: Status of the condition, one of True, False, Unknown. 128 | type: string 129 | type: 130 | description: Type is the type of the Condition 131 | type: string 132 | required: 133 | - status 134 | - type 135 | type: object 136 | type: array 137 | required: 138 | - conditions 139 | type: object 140 | type: object 141 | served: true 142 | storage: true 143 | subresources: 144 | status: {} 145 | -------------------------------------------------------------------------------- /pkg/tags/sync.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package tags 15 | 16 | import ( 17 | "context" 18 | 19 | "github.com/aws-controllers-k8s/acm-controller/apis/v1alpha1" 20 | ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" 21 | 22 | svcsdk "github.com/aws/aws-sdk-go-v2/service/acm" 23 | svcsdktypes "github.com/aws/aws-sdk-go-v2/service/acm/types" 24 | ) 25 | 26 | type metricsRecorder interface { 27 | RecordAPICall(opType string, opID string, err error) 28 | } 29 | 30 | type tagsClient interface { 31 | AddTagsToCertificate(context.Context, *svcsdk.AddTagsToCertificateInput, ...func(*svcsdk.Options)) (*svcsdk.AddTagsToCertificateOutput, error) 32 | ListTagsForCertificate(context.Context, *svcsdk.ListTagsForCertificateInput, ...func(*svcsdk.Options)) (*svcsdk.ListTagsForCertificateOutput, error) 33 | RemoveTagsFromCertificate(context.Context, *svcsdk.RemoveTagsFromCertificateInput, ...func(*svcsdk.Options)) (*svcsdk.RemoveTagsFromCertificateOutput, error) 34 | } 35 | 36 | // syncTags examines the Tags in the supplied Resource and calls the 37 | // TagResource and UntagResource APIs to ensure that the set of 38 | // associated Tags stays in sync with the Resource.Spec.Tags 39 | func SyncTags( 40 | ctx context.Context, 41 | client tagsClient, 42 | mr metricsRecorder, 43 | resourceID string, 44 | aTags []*v1alpha1.Tag, 45 | bTags []*v1alpha1.Tag, 46 | ) (err error) { 47 | rlog := ackrtlog.FromContext(ctx) 48 | exit := rlog.Trace("rm.syncTags") 49 | defer func() { exit(err) }() 50 | 51 | desiredTags := map[string]*string{} 52 | for _, t := range aTags { 53 | desiredTags[*t.Key] = t.Value 54 | } 55 | existingTags := map[string]*string{} 56 | for _, t := range bTags { 57 | existingTags[*t.Key] = t.Value 58 | } 59 | 60 | toAdd := map[string]*string{} 61 | toDelete := map[string]*string{} 62 | 63 | for k, v := range desiredTags { 64 | if ev, found := existingTags[k]; !found || *ev != *v { 65 | toAdd[k] = v 66 | } 67 | } 68 | 69 | for k, v := range existingTags { 70 | if _, found := desiredTags[k]; !found { 71 | toDelete[k] = v 72 | } 73 | } 74 | 75 | if len(toDelete) > 0 { 76 | for k, v := range toDelete { 77 | rlog.Debug("removing tag from resource", "key", k, "value", *v) 78 | } 79 | if err = removeTags( 80 | ctx, 81 | client, 82 | mr, 83 | resourceID, 84 | toDelete, 85 | ); err != nil { 86 | return err 87 | } 88 | } 89 | if len(toAdd) > 0 { 90 | for k, v := range toAdd { 91 | rlog.Debug("adding tag to resource", "key", k, "value", *v) 92 | } 93 | if err = addTags( 94 | ctx, 95 | client, 96 | mr, 97 | resourceID, 98 | toAdd, 99 | ); err != nil { 100 | return err 101 | } 102 | } 103 | 104 | return nil 105 | } 106 | 107 | // addTags adds the supplied Tags to the supplied resource 108 | func addTags( 109 | ctx context.Context, 110 | client tagsClient, 111 | mr metricsRecorder, 112 | resourceARN string, 113 | tags map[string]*string, 114 | ) (err error) { 115 | rlog := ackrtlog.FromContext(ctx) 116 | exit := rlog.Trace("rm.addTag") 117 | defer func() { exit(err) }() 118 | 119 | sdkTags := []svcsdktypes.Tag{} 120 | for k, v := range tags { 121 | k := k 122 | sdkTags = append(sdkTags, svcsdktypes.Tag{ 123 | Key: &k, 124 | Value: v, 125 | }) 126 | } 127 | 128 | input := &svcsdk.AddTagsToCertificateInput{ 129 | CertificateArn: &resourceARN, 130 | Tags: sdkTags, 131 | } 132 | 133 | _, err = client.AddTagsToCertificate(ctx, input) 134 | mr.RecordAPICall("UPDATE", "AddTagsToCertificate", err) 135 | return err 136 | } 137 | 138 | // removeTags removes the supplied Tags from the supplied resource 139 | func removeTags( 140 | ctx context.Context, 141 | client tagsClient, 142 | mr metricsRecorder, 143 | resourceARN string, 144 | tags map[string]*string, 145 | ) (err error) { 146 | rlog := ackrtlog.FromContext(ctx) 147 | exit := rlog.Trace("rm.removeTag") 148 | defer func() { exit(err) }() 149 | 150 | sdkTags := []svcsdktypes.Tag{} 151 | for k, v := range tags { 152 | k := k 153 | sdkTags = append(sdkTags, svcsdktypes.Tag{ 154 | Key: &k, 155 | Value: v, 156 | }) 157 | } 158 | 159 | input := &svcsdk.RemoveTagsFromCertificateInput{ 160 | CertificateArn: &resourceARN, 161 | Tags: sdkTags, 162 | } 163 | _, err = client.RemoveTagsFromCertificate(ctx, input) 164 | mr.RecordAPICall("UPDATE", "RemoveTagsFromCertificate", err) 165 | return err 166 | } 167 | 168 | // getResourceTagsPagesWithContext queries the list of tags of a given resource. 169 | func ListTags( 170 | ctx context.Context, 171 | client tagsClient, 172 | mr metricsRecorder, 173 | resourceARN string, 174 | ) ([]*v1alpha1.Tag, error) { 175 | var err error 176 | rlog := ackrtlog.FromContext(ctx) 177 | exit := rlog.Trace("rm.listTags") 178 | defer exit(err) 179 | 180 | var listTagsOfResourceOutput *svcsdk.ListTagsForCertificateOutput 181 | listTagsOfResourceOutput, err = client.ListTagsForCertificate( 182 | ctx, 183 | &svcsdk.ListTagsForCertificateInput{ 184 | CertificateArn: &resourceARN, 185 | }, 186 | ) 187 | mr.RecordAPICall("GET", "ListTagsForCertificate", err) 188 | if err != nil { 189 | return nil, err 190 | } 191 | return resourceTagsFromSDKTags(listTagsOfResourceOutput.Tags), nil 192 | } 193 | 194 | // resourceTagsFromSDKTags transforms a *svcsdk.Tag array to a *v1alpha1.Tag array. 195 | func resourceTagsFromSDKTags(svcTags []svcsdktypes.Tag) []*v1alpha1.Tag { 196 | tags := make([]*v1alpha1.Tag, len(svcTags)) 197 | for i := range svcTags { 198 | tags[i] = &v1alpha1.Tag{ 199 | Key: svcTags[i].Key, 200 | Value: svcTags[i].Value, 201 | } 202 | } 203 | return tags 204 | } 205 | -------------------------------------------------------------------------------- /cmd/controller/main.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package main 17 | 18 | import ( 19 | "context" 20 | "os" 21 | 22 | acmpcaapitypes "github.com/aws-controllers-k8s/acmpca-controller/apis/v1alpha1" 23 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 24 | ackcfg "github.com/aws-controllers-k8s/runtime/pkg/config" 25 | ackrt "github.com/aws-controllers-k8s/runtime/pkg/runtime" 26 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 27 | ackrtutil "github.com/aws-controllers-k8s/runtime/pkg/util" 28 | ackrtwebhook "github.com/aws-controllers-k8s/runtime/pkg/webhook" 29 | flag "github.com/spf13/pflag" 30 | "k8s.io/apimachinery/pkg/runtime" 31 | "k8s.io/apimachinery/pkg/runtime/schema" 32 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 33 | ctrlrt "sigs.k8s.io/controller-runtime" 34 | ctrlrtcache "sigs.k8s.io/controller-runtime/pkg/cache" 35 | ctrlrthealthz "sigs.k8s.io/controller-runtime/pkg/healthz" 36 | ctrlrtmetrics "sigs.k8s.io/controller-runtime/pkg/metrics" 37 | metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" 38 | ctrlrtwebhook "sigs.k8s.io/controller-runtime/pkg/webhook" 39 | 40 | svctypes "github.com/aws-controllers-k8s/acm-controller/apis/v1alpha1" 41 | svcresource "github.com/aws-controllers-k8s/acm-controller/pkg/resource" 42 | 43 | _ "github.com/aws-controllers-k8s/acm-controller/pkg/resource/certificate" 44 | 45 | "github.com/aws-controllers-k8s/acm-controller/pkg/version" 46 | ) 47 | 48 | var ( 49 | awsServiceAPIGroup = "acm.services.k8s.aws" 50 | awsServiceAlias = "acm" 51 | scheme = runtime.NewScheme() 52 | setupLog = ctrlrt.Log.WithName("setup") 53 | ) 54 | 55 | func init() { 56 | _ = clientgoscheme.AddToScheme(scheme) 57 | 58 | _ = svctypes.AddToScheme(scheme) 59 | _ = ackv1alpha1.AddToScheme(scheme) 60 | _ = acmpcaapitypes.AddToScheme(scheme) 61 | } 62 | 63 | func main() { 64 | var ackCfg ackcfg.Config 65 | ackCfg.BindFlags() 66 | flag.Parse() 67 | ackCfg.SetupLogger() 68 | 69 | managerFactories := svcresource.GetManagerFactories() 70 | resourceGVKs := make([]schema.GroupVersionKind, 0, len(managerFactories)) 71 | for _, mf := range managerFactories { 72 | resourceGVKs = append(resourceGVKs, mf.ResourceDescriptor().GroupVersionKind()) 73 | } 74 | 75 | ctx := context.Background() 76 | if err := ackCfg.Validate(ctx, ackcfg.WithGVKs(resourceGVKs)); err != nil { 77 | setupLog.Error( 78 | err, "Unable to create controller manager", 79 | "aws.service", awsServiceAlias, 80 | ) 81 | os.Exit(1) 82 | } 83 | 84 | host, port, err := ackrtutil.GetHostPort(ackCfg.WebhookServerAddr) 85 | if err != nil { 86 | setupLog.Error( 87 | err, "Unable to parse webhook server address.", 88 | "aws.service", awsServiceAlias, 89 | ) 90 | os.Exit(1) 91 | } 92 | 93 | watchNamespaces := make(map[string]ctrlrtcache.Config, 0) 94 | namespaces, err := ackCfg.GetWatchNamespaces() 95 | if err != nil { 96 | setupLog.Error( 97 | err, "Unable to parse watch namespaces.", 98 | "aws.service", ackCfg.WatchNamespace, 99 | ) 100 | os.Exit(1) 101 | } 102 | 103 | for _, namespace := range namespaces { 104 | watchNamespaces[namespace] = ctrlrtcache.Config{} 105 | } 106 | watchSelectors, err := ackCfg.ParseWatchSelectors() 107 | if err != nil { 108 | setupLog.Error( 109 | err, "Unable to parse watch selectors.", 110 | "aws.service", awsServiceAlias, 111 | ) 112 | os.Exit(1) 113 | } 114 | mgr, err := ctrlrt.NewManager(ctrlrt.GetConfigOrDie(), ctrlrt.Options{ 115 | Scheme: scheme, 116 | Cache: ctrlrtcache.Options{ 117 | Scheme: scheme, 118 | DefaultNamespaces: watchNamespaces, 119 | DefaultLabelSelector: watchSelectors, 120 | }, 121 | WebhookServer: &ctrlrtwebhook.DefaultServer{ 122 | Options: ctrlrtwebhook.Options{ 123 | Port: port, 124 | Host: host, 125 | }, 126 | }, 127 | Metrics: metricsserver.Options{BindAddress: ackCfg.MetricsAddr}, 128 | LeaderElection: ackCfg.EnableLeaderElection, 129 | LeaderElectionID: "ack-" + awsServiceAPIGroup, 130 | LeaderElectionNamespace: ackCfg.LeaderElectionNamespace, 131 | HealthProbeBindAddress: ackCfg.HealthzAddr, 132 | LivenessEndpointName: "/healthz", 133 | ReadinessEndpointName: "/readyz", 134 | }) 135 | if err != nil { 136 | setupLog.Error( 137 | err, "unable to create controller manager", 138 | "aws.service", awsServiceAlias, 139 | ) 140 | os.Exit(1) 141 | } 142 | 143 | stopChan := ctrlrt.SetupSignalHandler() 144 | 145 | setupLog.Info( 146 | "initializing service controller", 147 | "aws.service", awsServiceAlias, 148 | ) 149 | sc := ackrt.NewServiceController( 150 | awsServiceAlias, awsServiceAPIGroup, 151 | acktypes.VersionInfo{ 152 | version.GitCommit, 153 | version.GitVersion, 154 | version.BuildDate, 155 | }, 156 | ).WithLogger( 157 | ctrlrt.Log, 158 | ).WithResourceManagerFactories( 159 | svcresource.GetManagerFactories(), 160 | ).WithPrometheusRegistry( 161 | ctrlrtmetrics.Registry, 162 | ) 163 | 164 | if ackCfg.EnableWebhookServer { 165 | webhooks := ackrtwebhook.GetWebhooks() 166 | for _, webhook := range webhooks { 167 | if err := webhook.Setup(mgr); err != nil { 168 | setupLog.Error( 169 | err, "unable to register webhook "+webhook.UID(), 170 | "aws.service", awsServiceAlias, 171 | ) 172 | } 173 | } 174 | } 175 | 176 | if err = sc.BindControllerManager(mgr, ackCfg); err != nil { 177 | setupLog.Error( 178 | err, "unable bind to controller manager to service controller", 179 | "aws.service", awsServiceAlias, 180 | ) 181 | os.Exit(1) 182 | } 183 | 184 | if err = mgr.AddHealthzCheck("health", ctrlrthealthz.Ping); err != nil { 185 | setupLog.Error( 186 | err, "unable to set up health check", 187 | "aws.service", awsServiceAlias, 188 | ) 189 | os.Exit(1) 190 | } 191 | if err = mgr.AddReadyzCheck("check", ctrlrthealthz.Ping); err != nil { 192 | setupLog.Error( 193 | err, "unable to set up ready check", 194 | "aws.service", awsServiceAlias, 195 | ) 196 | os.Exit(1) 197 | } 198 | 199 | setupLog.Info( 200 | "starting manager", 201 | "aws.service", awsServiceAlias, 202 | ) 203 | if err := mgr.Start(stopChan); err != nil { 204 | setupLog.Error( 205 | err, "unable to start controller manager", 206 | "aws.service", awsServiceAlias, 207 | ) 208 | os.Exit(1) 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /generator.yaml: -------------------------------------------------------------------------------- 1 | ignore: 2 | field_paths: 3 | - "RequestCertificateInput.IdempotencyToken" 4 | - "RequestCertificateInput.ValidationMethod" 5 | operations: 6 | RequestCertificate: 7 | resource_name: Certificate 8 | operation_type: CREATE 9 | # NOTE(jaypipes): There is a GetCertificate API call, but that returns the 10 | # actual cert bytes, not the attributes of the certificate request 11 | DescribeCertificate: 12 | resource_name: Certificate 13 | operation_type: READ_ONE 14 | UpdateCertificateOptions: 15 | resource_name: Certificate 16 | operation_type: UPDATE 17 | resources: 18 | Certificate: 19 | hooks: 20 | delta_pre_compare: 21 | template_path: hooks/certificate/delta_pre_compare.go.tpl 22 | sdk_update_pre_build_request: 23 | template_path: hooks/certificate/sdk_update_pre_build_request.go.tpl 24 | sdk_update_post_set_output: 25 | template_path: hooks/certificate/sdk_update_post_set_output.go.tpl 26 | sdk_create_pre_build_request: 27 | template_path: hooks/certificate/sdk_create_pre_build_request.go.tpl 28 | sdk_create_post_build_request: 29 | template_path: hooks/certificate/sdk_create_post_build_request.go.tpl 30 | sdk_read_one_pre_set_output: 31 | template_path: hooks/certificate/sdk_read_one_pre_set_output.go.tpl 32 | sdk_file_end: 33 | template_path: hooks/certificate/sdk_file_end.go.tpl 34 | late_initialize_post_read_one: 35 | template_path: hooks/certificate/late_initialize_post_read_one.go.tpl 36 | exceptions: 37 | errors: 38 | 404: 39 | code: ResourceNotFoundException 40 | terminal_codes: 41 | - InvalidParameterException 42 | - InvalidDomainValidationOptionsException 43 | - InvalidTagException 44 | - TagPolicyException 45 | - TooManyTagsException 46 | - InvalidArnException 47 | reconcile: 48 | requeue_on_success_seconds: 60 49 | fields: 50 | ExportTo: 51 | type: "bytes" 52 | is_immutable: true 53 | is_secret: true 54 | compare: 55 | is_ignored: true 56 | DomainName: 57 | is_primary_key: false 58 | is_required: false 59 | Certificate: 60 | type: "bytes" 61 | is_secret: true 62 | is_immutable: true 63 | compare: 64 | is_ignored: true 65 | PrivateKey: 66 | type: "bytes" 67 | is_secret: true 68 | is_immutable: true 69 | compare: 70 | is_ignored: true 71 | CertificateArn: 72 | type: string 73 | is_immutable: true 74 | CertificateChain: 75 | type: "bytes" 76 | is_immutable: true 77 | is_secret: true 78 | compare: 79 | is_ignored: true 80 | CertificateAuthorityARN: 81 | references: 82 | service_name: acmpca 83 | resource: CertificateAuthority 84 | path: Status.ACKResourceMetadata.ARN 85 | is_immutable: true 86 | KeyAlgorithm: 87 | late_initialize: {} 88 | is_immutable: true 89 | compare: 90 | is_ignored: true 91 | Options: 92 | late_initialize: {} 93 | # NOTE(jaypipes): The Create operation (RequestCertificate) has a 94 | # response with only a single field (certificateArn). All of the status 95 | # fields for the certificate are in the ReadOne operation 96 | # (DescribeCertificate) response, so we need to tell the code-generator 97 | # about all of those fields manually here... 98 | CreatedAt: 99 | is_read_only: true 100 | from: 101 | operation: DescribeCertificate 102 | path: Certificate.CreatedAt 103 | DomainValidations: 104 | is_read_only: true 105 | from: 106 | operation: DescribeCertificate 107 | path: Certificate.DomainValidationOptions 108 | DomainValidationOptions: 109 | is_immutable: true 110 | compare: 111 | is_ignored: true 112 | ExtendedKeyUsages: 113 | is_read_only: true 114 | from: 115 | operation: DescribeCertificate 116 | path: Certificate.ExtendedKeyUsages 117 | FailureReason: 118 | is_read_only: true 119 | from: 120 | operation: DescribeCertificate 121 | path: Certificate.FailureReason 122 | ImportedAt: 123 | is_read_only: true 124 | from: 125 | operation: DescribeCertificate 126 | path: Certificate.ImportedAt 127 | InUseBy: 128 | is_read_only: true 129 | from: 130 | operation: DescribeCertificate 131 | path: Certificate.InUseBy 132 | IssuedAt: 133 | is_read_only: true 134 | from: 135 | operation: DescribeCertificate 136 | path: Certificate.IssuedAt 137 | Issuer: 138 | is_read_only: true 139 | from: 140 | operation: DescribeCertificate 141 | path: Certificate.Issuer 142 | KeyUsages: 143 | is_read_only: true 144 | from: 145 | operation: DescribeCertificate 146 | path: Certificate.KeyUsages 147 | NotAfter: 148 | is_read_only: true 149 | from: 150 | operation: DescribeCertificate 151 | path: Certificate.NotAfter 152 | NotBefore: 153 | is_read_only: true 154 | from: 155 | operation: DescribeCertificate 156 | path: Certificate.NotBefore 157 | RenewalEligibility: 158 | is_read_only: true 159 | from: 160 | operation: DescribeCertificate 161 | path: Certificate.RenewalEligibility 162 | RenewalSummary: 163 | is_read_only: true 164 | from: 165 | operation: DescribeCertificate 166 | path: Certificate.RenewalSummary 167 | RevocationReason: 168 | is_read_only: true 169 | from: 170 | operation: DescribeCertificate 171 | path: Certificate.RevocationReason 172 | RevokedAt: 173 | is_read_only: true 174 | from: 175 | operation: DescribeCertificate 176 | path: Certificate.RevokedAt 177 | Serial: 178 | is_read_only: true 179 | from: 180 | operation: DescribeCertificate 181 | path: Certificate.Serial 182 | SignatureAlgorithm: 183 | is_read_only: true 184 | from: 185 | operation: DescribeCertificate 186 | path: Certificate.SignatureAlgorithm 187 | Status: 188 | is_read_only: true 189 | from: 190 | operation: DescribeCertificate 191 | path: Certificate.Status 192 | Subject: 193 | is_read_only: true 194 | from: 195 | operation: DescribeCertificate 196 | path: Certificate.Subject 197 | Type: 198 | is_read_only: true 199 | from: 200 | operation: DescribeCertificate 201 | path: Certificate.Type 202 | -------------------------------------------------------------------------------- /apis/v1alpha1/generator.yaml: -------------------------------------------------------------------------------- 1 | ignore: 2 | field_paths: 3 | - "RequestCertificateInput.IdempotencyToken" 4 | - "RequestCertificateInput.ValidationMethod" 5 | operations: 6 | RequestCertificate: 7 | resource_name: Certificate 8 | operation_type: CREATE 9 | # NOTE(jaypipes): There is a GetCertificate API call, but that returns the 10 | # actual cert bytes, not the attributes of the certificate request 11 | DescribeCertificate: 12 | resource_name: Certificate 13 | operation_type: READ_ONE 14 | UpdateCertificateOptions: 15 | resource_name: Certificate 16 | operation_type: UPDATE 17 | resources: 18 | Certificate: 19 | hooks: 20 | delta_pre_compare: 21 | template_path: hooks/certificate/delta_pre_compare.go.tpl 22 | sdk_update_pre_build_request: 23 | template_path: hooks/certificate/sdk_update_pre_build_request.go.tpl 24 | sdk_update_post_set_output: 25 | template_path: hooks/certificate/sdk_update_post_set_output.go.tpl 26 | sdk_create_pre_build_request: 27 | template_path: hooks/certificate/sdk_create_pre_build_request.go.tpl 28 | sdk_create_post_build_request: 29 | template_path: hooks/certificate/sdk_create_post_build_request.go.tpl 30 | sdk_read_one_pre_set_output: 31 | template_path: hooks/certificate/sdk_read_one_pre_set_output.go.tpl 32 | sdk_file_end: 33 | template_path: hooks/certificate/sdk_file_end.go.tpl 34 | late_initialize_post_read_one: 35 | template_path: hooks/certificate/late_initialize_post_read_one.go.tpl 36 | exceptions: 37 | errors: 38 | 404: 39 | code: ResourceNotFoundException 40 | terminal_codes: 41 | - InvalidParameterException 42 | - InvalidDomainValidationOptionsException 43 | - InvalidTagException 44 | - TagPolicyException 45 | - TooManyTagsException 46 | - InvalidArnException 47 | reconcile: 48 | requeue_on_success_seconds: 60 49 | fields: 50 | ExportTo: 51 | type: "bytes" 52 | is_immutable: true 53 | is_secret: true 54 | compare: 55 | is_ignored: true 56 | DomainName: 57 | is_primary_key: false 58 | is_required: false 59 | Certificate: 60 | type: "bytes" 61 | is_secret: true 62 | is_immutable: true 63 | compare: 64 | is_ignored: true 65 | PrivateKey: 66 | type: "bytes" 67 | is_secret: true 68 | is_immutable: true 69 | compare: 70 | is_ignored: true 71 | CertificateArn: 72 | type: string 73 | is_immutable: true 74 | CertificateChain: 75 | type: "bytes" 76 | is_immutable: true 77 | is_secret: true 78 | compare: 79 | is_ignored: true 80 | CertificateAuthorityARN: 81 | references: 82 | service_name: acmpca 83 | resource: CertificateAuthority 84 | path: Status.ACKResourceMetadata.ARN 85 | is_immutable: true 86 | KeyAlgorithm: 87 | late_initialize: {} 88 | is_immutable: true 89 | compare: 90 | is_ignored: true 91 | Options: 92 | late_initialize: {} 93 | # NOTE(jaypipes): The Create operation (RequestCertificate) has a 94 | # response with only a single field (certificateArn). All of the status 95 | # fields for the certificate are in the ReadOne operation 96 | # (DescribeCertificate) response, so we need to tell the code-generator 97 | # about all of those fields manually here... 98 | CreatedAt: 99 | is_read_only: true 100 | from: 101 | operation: DescribeCertificate 102 | path: Certificate.CreatedAt 103 | DomainValidations: 104 | is_read_only: true 105 | from: 106 | operation: DescribeCertificate 107 | path: Certificate.DomainValidationOptions 108 | DomainValidationOptions: 109 | is_immutable: true 110 | compare: 111 | is_ignored: true 112 | ExtendedKeyUsages: 113 | is_read_only: true 114 | from: 115 | operation: DescribeCertificate 116 | path: Certificate.ExtendedKeyUsages 117 | FailureReason: 118 | is_read_only: true 119 | from: 120 | operation: DescribeCertificate 121 | path: Certificate.FailureReason 122 | ImportedAt: 123 | is_read_only: true 124 | from: 125 | operation: DescribeCertificate 126 | path: Certificate.ImportedAt 127 | InUseBy: 128 | is_read_only: true 129 | from: 130 | operation: DescribeCertificate 131 | path: Certificate.InUseBy 132 | IssuedAt: 133 | is_read_only: true 134 | from: 135 | operation: DescribeCertificate 136 | path: Certificate.IssuedAt 137 | Issuer: 138 | is_read_only: true 139 | from: 140 | operation: DescribeCertificate 141 | path: Certificate.Issuer 142 | KeyUsages: 143 | is_read_only: true 144 | from: 145 | operation: DescribeCertificate 146 | path: Certificate.KeyUsages 147 | NotAfter: 148 | is_read_only: true 149 | from: 150 | operation: DescribeCertificate 151 | path: Certificate.NotAfter 152 | NotBefore: 153 | is_read_only: true 154 | from: 155 | operation: DescribeCertificate 156 | path: Certificate.NotBefore 157 | RenewalEligibility: 158 | is_read_only: true 159 | from: 160 | operation: DescribeCertificate 161 | path: Certificate.RenewalEligibility 162 | RenewalSummary: 163 | is_read_only: true 164 | from: 165 | operation: DescribeCertificate 166 | path: Certificate.RenewalSummary 167 | RevocationReason: 168 | is_read_only: true 169 | from: 170 | operation: DescribeCertificate 171 | path: Certificate.RevocationReason 172 | RevokedAt: 173 | is_read_only: true 174 | from: 175 | operation: DescribeCertificate 176 | path: Certificate.RevokedAt 177 | Serial: 178 | is_read_only: true 179 | from: 180 | operation: DescribeCertificate 181 | path: Certificate.Serial 182 | SignatureAlgorithm: 183 | is_read_only: true 184 | from: 185 | operation: DescribeCertificate 186 | path: Certificate.SignatureAlgorithm 187 | Status: 188 | is_read_only: true 189 | from: 190 | operation: DescribeCertificate 191 | path: Certificate.Status 192 | Subject: 193 | is_read_only: true 194 | from: 195 | operation: DescribeCertificate 196 | path: Certificate.Subject 197 | Type: 198 | is_read_only: true 199 | from: 200 | operation: DescribeCertificate 201 | path: Certificate.Type 202 | -------------------------------------------------------------------------------- /pkg/resource/certificate/references.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package certificate 17 | 18 | import ( 19 | "context" 20 | "fmt" 21 | 22 | corev1 "k8s.io/api/core/v1" 23 | "k8s.io/apimachinery/pkg/types" 24 | "sigs.k8s.io/controller-runtime/pkg/client" 25 | 26 | acmpcaapitypes "github.com/aws-controllers-k8s/acmpca-controller/apis/v1alpha1" 27 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 28 | ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" 29 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 30 | 31 | svcapitypes "github.com/aws-controllers-k8s/acm-controller/apis/v1alpha1" 32 | ) 33 | 34 | // +kubebuilder:rbac:groups=acmpca.services.k8s.aws,resources=certificateauthorities,verbs=get;list 35 | // +kubebuilder:rbac:groups=acmpca.services.k8s.aws,resources=certificateauthorities/status,verbs=get;list 36 | 37 | // ClearResolvedReferences removes any reference values that were made 38 | // concrete in the spec. It returns a copy of the input AWSResource which 39 | // contains the original *Ref values, but none of their respective concrete 40 | // values. 41 | func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) acktypes.AWSResource { 42 | ko := rm.concreteResource(res).ko.DeepCopy() 43 | 44 | if ko.Spec.CertificateAuthorityRef != nil { 45 | ko.Spec.CertificateAuthorityARN = nil 46 | } 47 | 48 | return &resource{ko} 49 | } 50 | 51 | // ResolveReferences finds if there are any Reference field(s) present 52 | // inside AWSResource passed in the parameter and attempts to resolve those 53 | // reference field(s) into their respective target field(s). It returns a 54 | // copy of the input AWSResource with resolved reference(s), a boolean which 55 | // is set to true if the resource contains any references (regardless of if 56 | // they are resolved successfully) and an error if the passed AWSResource's 57 | // reference field(s) could not be resolved. 58 | func (rm *resourceManager) ResolveReferences( 59 | ctx context.Context, 60 | apiReader client.Reader, 61 | res acktypes.AWSResource, 62 | ) (acktypes.AWSResource, bool, error) { 63 | ko := rm.concreteResource(res).ko 64 | 65 | resourceHasReferences := false 66 | err := validateReferenceFields(ko) 67 | if fieldHasReferences, err := rm.resolveReferenceForCertificateAuthorityARN(ctx, apiReader, ko); err != nil { 68 | return &resource{ko}, (resourceHasReferences || fieldHasReferences), err 69 | } else { 70 | resourceHasReferences = resourceHasReferences || fieldHasReferences 71 | } 72 | 73 | return &resource{ko}, resourceHasReferences, err 74 | } 75 | 76 | // validateReferenceFields validates the reference field and corresponding 77 | // identifier field. 78 | func validateReferenceFields(ko *svcapitypes.Certificate) error { 79 | 80 | if ko.Spec.CertificateAuthorityRef != nil && ko.Spec.CertificateAuthorityARN != nil { 81 | return ackerr.ResourceReferenceAndIDNotSupportedFor("CertificateAuthorityARN", "CertificateAuthorityRef") 82 | } 83 | return nil 84 | } 85 | 86 | // resolveReferenceForCertificateAuthorityARN reads the resource referenced 87 | // from CertificateAuthorityRef field and sets the CertificateAuthorityARN 88 | // from referenced resource. Returns a boolean indicating whether a reference 89 | // contains references, or an error 90 | func (rm *resourceManager) resolveReferenceForCertificateAuthorityARN( 91 | ctx context.Context, 92 | apiReader client.Reader, 93 | ko *svcapitypes.Certificate, 94 | ) (hasReferences bool, err error) { 95 | if ko.Spec.CertificateAuthorityRef != nil && ko.Spec.CertificateAuthorityRef.From != nil { 96 | hasReferences = true 97 | arr := ko.Spec.CertificateAuthorityRef.From 98 | if arr.Name == nil || *arr.Name == "" { 99 | return hasReferences, fmt.Errorf("provided resource reference is nil or empty: CertificateAuthorityRef") 100 | } 101 | namespace := ko.ObjectMeta.GetNamespace() 102 | if arr.Namespace != nil && *arr.Namespace != "" { 103 | namespace = *arr.Namespace 104 | } 105 | obj := &acmpcaapitypes.CertificateAuthority{} 106 | if err := getReferencedResourceState_CertificateAuthority(ctx, apiReader, obj, *arr.Name, namespace); err != nil { 107 | return hasReferences, err 108 | } 109 | ko.Spec.CertificateAuthorityARN = (*string)(obj.Status.ACKResourceMetadata.ARN) 110 | } 111 | 112 | return hasReferences, nil 113 | } 114 | 115 | // getReferencedResourceState_CertificateAuthority looks up whether a referenced resource 116 | // exists and is in a ACK.ResourceSynced=True state. If the referenced resource does exist and is 117 | // in a Synced state, returns nil, otherwise returns `ackerr.ResourceReferenceTerminalFor` or 118 | // `ResourceReferenceNotSyncedFor` depending on if the resource is in a Terminal state. 119 | func getReferencedResourceState_CertificateAuthority( 120 | ctx context.Context, 121 | apiReader client.Reader, 122 | obj *acmpcaapitypes.CertificateAuthority, 123 | name string, // the Kubernetes name of the referenced resource 124 | namespace string, // the Kubernetes namespace of the referenced resource 125 | ) error { 126 | namespacedName := types.NamespacedName{ 127 | Namespace: namespace, 128 | Name: name, 129 | } 130 | err := apiReader.Get(ctx, namespacedName, obj) 131 | if err != nil { 132 | return err 133 | } 134 | var refResourceTerminal bool 135 | for _, cond := range obj.Status.Conditions { 136 | if cond.Type == ackv1alpha1.ConditionTypeTerminal && 137 | cond.Status == corev1.ConditionTrue { 138 | return ackerr.ResourceReferenceTerminalFor( 139 | "CertificateAuthority", 140 | namespace, name) 141 | } 142 | } 143 | if refResourceTerminal { 144 | return ackerr.ResourceReferenceTerminalFor( 145 | "CertificateAuthority", 146 | namespace, name) 147 | } 148 | var refResourceSynced bool 149 | for _, cond := range obj.Status.Conditions { 150 | if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && 151 | cond.Status == corev1.ConditionTrue { 152 | refResourceSynced = true 153 | } 154 | } 155 | if !refResourceSynced { 156 | return ackerr.ResourceReferenceNotSyncedFor( 157 | "CertificateAuthority", 158 | namespace, name) 159 | } 160 | if obj.Status.ACKResourceMetadata == nil || obj.Status.ACKResourceMetadata.ARN == nil { 161 | return ackerr.ResourceReferenceMissingTargetFieldFor( 162 | "CertificateAuthority", 163 | namespace, name, 164 | "Status.ACKResourceMetadata.ARN") 165 | } 166 | return nil 167 | } 168 | -------------------------------------------------------------------------------- /helm/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for ack-acm-controller. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | image: 6 | repository: public.ecr.aws/aws-controllers-k8s/acm-controller 7 | tag: 1.3.0 8 | pullPolicy: IfNotPresent 9 | pullSecrets: [] 10 | 11 | nameOverride: "" 12 | fullnameOverride: "" 13 | 14 | deployment: 15 | annotations: {} 16 | labels: {} 17 | containerPort: 8080 18 | # Number of Deployment replicas 19 | # This determines how many instances of the controller will be running. It's recommended 20 | # to enable leader election if you need to increase the number of replicas > 1 21 | replicas: 1 22 | # Which nodeSelector to set? 23 | # See: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector 24 | nodeSelector: 25 | kubernetes.io/os: linux 26 | # Which tolerations to set? 27 | # See: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ 28 | tolerations: [] 29 | # What affinity to set? 30 | # See: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity 31 | affinity: {} 32 | # Which priorityClassName to set? 33 | # See: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/#pod-priority 34 | priorityClassName: "" 35 | # Specifies the hostname of the Pod. 36 | # If not specified, the pod's hostname will be set to a system-defined value. 37 | hostNetwork: false 38 | # Set DNS policy for the pod. 39 | # Defaults to "ClusterFirst". 40 | # Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. 41 | # To have DNS options set along with hostNetwork, you have to specify DNS policy 42 | # explicitly to 'ClusterFirstWithHostNet'. 43 | dnsPolicy: ClusterFirst 44 | # Set rollout strategy for deployment. 45 | # See: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy 46 | strategy: {} 47 | extraVolumes: [] 48 | extraVolumeMounts: [] 49 | 50 | # Additional server container environment variables 51 | # 52 | # You specify this manually like you would a raw deployment manifest. 53 | # This means you can bind in environment variables from secrets. 54 | # 55 | # e.g. static environment variable: 56 | # - name: DEMO_GREETING 57 | # value: "Hello from the environment" 58 | # 59 | # e.g. secret environment variable: 60 | # - name: USERNAME 61 | # valueFrom: 62 | # secretKeyRef: 63 | # name: mysecret 64 | # key: username 65 | extraEnvVars: [] 66 | 67 | 68 | # If "installScope: cluster" then these labels will be applied to ClusterRole 69 | role: 70 | labels: {} 71 | 72 | metrics: 73 | service: 74 | # Set to true to automatically create a Kubernetes Service resource for the 75 | # Prometheus metrics server endpoint in controller 76 | create: false 77 | # Which Type to use for the Kubernetes Service? 78 | # See: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types 79 | type: "ClusterIP" 80 | 81 | resources: 82 | requests: 83 | memory: "64Mi" 84 | cpu: "50m" 85 | limits: 86 | memory: "128Mi" 87 | cpu: "100m" 88 | 89 | aws: 90 | # If specified, use the AWS region for AWS API calls 91 | region: "" 92 | endpoint_url: "" 93 | identity_endpoint_url: "" 94 | allow_unsafe_aws_endpoint_urls: false 95 | credentials: 96 | # If specified, Secret with shared credentials file to use. 97 | secretName: "" 98 | # Secret stringData key that contains the credentials 99 | secretKey: "credentials" 100 | # Profile used for AWS credentials 101 | profile: "default" 102 | 103 | # log level for the controller 104 | log: 105 | enable_development_logging: false 106 | level: info 107 | 108 | # Set to "namespace" to install the controller in a namespaced scope, will only 109 | # watch for object creation in the namespace. By default installScope is 110 | # cluster wide. 111 | installScope: cluster 112 | 113 | # Set the value of the "namespace" to be watched by the controller 114 | # This value is only used when the `installScope` is set to "namespace". If left empty, the default value is the release namespace for the chart. 115 | # You can set multiple namespaces by providing a comma separated list of namespaces. e.g "namespace1,namespace2" 116 | watchNamespace: "" 117 | 118 | # Set the value of labelsSelectors to be used by the controller to filter the resources to watch. 119 | # You can set multiple labelsSelectors by providing a comma separated list of a=b arguments. e.g "label1=value1,label2=value2" 120 | watchSelectors: "" 121 | 122 | resourceTags: 123 | # Configures the ACK service controller to always set key/value pairs tags on 124 | # resources that it manages. 125 | # Note: Tags with empty values are automatically skipped to keep resources clean. 126 | - services.k8s.aws/controller-version=%CONTROLLER_SERVICE%-%CONTROLLER_VERSION% 127 | - services.k8s.aws/namespace=%K8S_NAMESPACE% 128 | - app.kubernetes.io/managed-by=%MANAGED_BY% 129 | - kro.run/kro-version=%KRO_VERSION% 130 | 131 | # Set to "retain" to keep all AWS resources intact even after the K8s resources 132 | # have been deleted. By default, the ACK controller will delete the AWS resource 133 | # before the K8s resource is removed. 134 | deletionPolicy: delete 135 | 136 | # controller reconciliation configurations 137 | reconcile: 138 | # The default duration, in seconds, to wait before resyncing desired state of custom resources. 139 | defaultResyncPeriod: 36000 # 10 Hours 140 | # An object representing the reconcile resync configuration for each specific resource. 141 | resourceResyncPeriods: {} 142 | 143 | # The default number of concurrent syncs that a reconciler can perform. 144 | defaultMaxConcurrentSyncs: 1 145 | # An object representing the reconcile max concurrent syncs configuration for each specific 146 | # resource. 147 | resourceMaxConcurrentSyncs: {} 148 | 149 | # Set the value of resources to specify which resource kinds to reconcile. 150 | # If empty, all resources will be reconciled. 151 | # If specified, only the listed resource kinds will be reconciled. 152 | resources: 153 | - Certificate 154 | 155 | serviceAccount: 156 | # Specifies whether a service account should be created 157 | create: true 158 | # The name of the service account to use. 159 | name: ack-acm-controller 160 | annotations: {} 161 | # eks.amazonaws.com/role-arn: arn:aws:iam::AWS_ACCOUNT_ID:role/IAM_ROLE_NAME 162 | 163 | # Configuration of the leader election. Required for running multiple instances of the 164 | # controller within the same cluster. 165 | # See https://kubernetes.io/docs/concepts/architecture/leases/#leader-election 166 | leaderElection: 167 | # Enable Controller Leader Election. Set this to true to enable leader election 168 | # for this controller. 169 | enabled: false 170 | # Leader election can be scoped to a specific namespace. By default, the controller 171 | # will attempt to use the namespace of the service account mounted to the Controller 172 | # pod. 173 | namespace: "" 174 | 175 | # Enable Cross Account Resource Management (default = true). Set this to false to disable cross account resource management. 176 | enableCARM: true 177 | 178 | # Configuration for feature gates. These are optional controller features that 179 | # can be individually enabled ("true") or disabled ("false") by adding key/value 180 | # pairs below. 181 | featureGates: 182 | # Enables the Service level granularity for CARM. See https://github.com/aws-controllers-k8s/community/issues/2031 183 | ServiceLevelCARM: false 184 | # Enables the Team level granularity for CARM. See https://github.com/aws-controllers-k8s/community/issues/2031 185 | TeamLevelCARM: false 186 | # Enable ReadOnlyResources feature/annotation. 187 | ReadOnlyResources: true 188 | # Enable ResourceAdoption feature/annotation. 189 | ResourceAdoption: true 190 | # Enable IAMRoleSelector, a multirole feature, replacing CARM. See https://github.com/aws-controllers-k8s/community/pull/2628 191 | IAMRoleSelector: false -------------------------------------------------------------------------------- /apis/v1alpha1/enums.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package v1alpha1 17 | 18 | type CertificateStatus_SDK string 19 | 20 | const ( 21 | CertificateStatus_SDK_EXPIRED CertificateStatus_SDK = "EXPIRED" 22 | CertificateStatus_SDK_FAILED CertificateStatus_SDK = "FAILED" 23 | CertificateStatus_SDK_INACTIVE CertificateStatus_SDK = "INACTIVE" 24 | CertificateStatus_SDK_ISSUED CertificateStatus_SDK = "ISSUED" 25 | CertificateStatus_SDK_PENDING_VALIDATION CertificateStatus_SDK = "PENDING_VALIDATION" 26 | CertificateStatus_SDK_REVOKED CertificateStatus_SDK = "REVOKED" 27 | CertificateStatus_SDK_VALIDATION_TIMED_OUT CertificateStatus_SDK = "VALIDATION_TIMED_OUT" 28 | ) 29 | 30 | type CertificateTransparencyLoggingPreference string 31 | 32 | const ( 33 | CertificateTransparencyLoggingPreference_DISABLED CertificateTransparencyLoggingPreference = "DISABLED" 34 | CertificateTransparencyLoggingPreference_ENABLED CertificateTransparencyLoggingPreference = "ENABLED" 35 | ) 36 | 37 | type CertificateType string 38 | 39 | const ( 40 | CertificateType_AMAZON_ISSUED CertificateType = "AMAZON_ISSUED" 41 | CertificateType_IMPORTED CertificateType = "IMPORTED" 42 | CertificateType_PRIVATE CertificateType = "PRIVATE" 43 | ) 44 | 45 | type DomainStatus string 46 | 47 | const ( 48 | DomainStatus_FAILED DomainStatus = "FAILED" 49 | DomainStatus_PENDING_VALIDATION DomainStatus = "PENDING_VALIDATION" 50 | DomainStatus_SUCCESS DomainStatus = "SUCCESS" 51 | ) 52 | 53 | type ExtendedKeyUsageName string 54 | 55 | const ( 56 | ExtendedKeyUsageName_ANY ExtendedKeyUsageName = "ANY" 57 | ExtendedKeyUsageName_CODE_SIGNING ExtendedKeyUsageName = "CODE_SIGNING" 58 | ExtendedKeyUsageName_CUSTOM ExtendedKeyUsageName = "CUSTOM" 59 | ExtendedKeyUsageName_EMAIL_PROTECTION ExtendedKeyUsageName = "EMAIL_PROTECTION" 60 | ExtendedKeyUsageName_IPSEC_END_SYSTEM ExtendedKeyUsageName = "IPSEC_END_SYSTEM" 61 | ExtendedKeyUsageName_IPSEC_TUNNEL ExtendedKeyUsageName = "IPSEC_TUNNEL" 62 | ExtendedKeyUsageName_IPSEC_USER ExtendedKeyUsageName = "IPSEC_USER" 63 | ExtendedKeyUsageName_NONE ExtendedKeyUsageName = "NONE" 64 | ExtendedKeyUsageName_OCSP_SIGNING ExtendedKeyUsageName = "OCSP_SIGNING" 65 | ExtendedKeyUsageName_TIME_STAMPING ExtendedKeyUsageName = "TIME_STAMPING" 66 | ExtendedKeyUsageName_TLS_WEB_CLIENT_AUTHENTICATION ExtendedKeyUsageName = "TLS_WEB_CLIENT_AUTHENTICATION" 67 | ExtendedKeyUsageName_TLS_WEB_SERVER_AUTHENTICATION ExtendedKeyUsageName = "TLS_WEB_SERVER_AUTHENTICATION" 68 | ) 69 | 70 | type FailureReason string 71 | 72 | const ( 73 | FailureReason_ADDITIONAL_VERIFICATION_REQUIRED FailureReason = "ADDITIONAL_VERIFICATION_REQUIRED" 74 | FailureReason_CAA_ERROR FailureReason = "CAA_ERROR" 75 | FailureReason_DOMAIN_NOT_ALLOWED FailureReason = "DOMAIN_NOT_ALLOWED" 76 | FailureReason_DOMAIN_VALIDATION_DENIED FailureReason = "DOMAIN_VALIDATION_DENIED" 77 | FailureReason_INVALID_PUBLIC_DOMAIN FailureReason = "INVALID_PUBLIC_DOMAIN" 78 | FailureReason_NO_AVAILABLE_CONTACTS FailureReason = "NO_AVAILABLE_CONTACTS" 79 | FailureReason_OTHER FailureReason = "OTHER" 80 | FailureReason_PCA_ACCESS_DENIED FailureReason = "PCA_ACCESS_DENIED" 81 | FailureReason_PCA_INVALID_ARGS FailureReason = "PCA_INVALID_ARGS" 82 | FailureReason_PCA_INVALID_ARN FailureReason = "PCA_INVALID_ARN" 83 | FailureReason_PCA_INVALID_DURATION FailureReason = "PCA_INVALID_DURATION" 84 | FailureReason_PCA_INVALID_STATE FailureReason = "PCA_INVALID_STATE" 85 | FailureReason_PCA_LIMIT_EXCEEDED FailureReason = "PCA_LIMIT_EXCEEDED" 86 | FailureReason_PCA_NAME_CONSTRAINTS_VALIDATION FailureReason = "PCA_NAME_CONSTRAINTS_VALIDATION" 87 | FailureReason_PCA_REQUEST_FAILED FailureReason = "PCA_REQUEST_FAILED" 88 | FailureReason_PCA_RESOURCE_NOT_FOUND FailureReason = "PCA_RESOURCE_NOT_FOUND" 89 | FailureReason_SLR_NOT_FOUND FailureReason = "SLR_NOT_FOUND" 90 | ) 91 | 92 | type KeyAlgorithm string 93 | 94 | const ( 95 | KeyAlgorithm_EC_prime256v1 KeyAlgorithm = "EC_prime256v1" 96 | KeyAlgorithm_EC_secp384r1 KeyAlgorithm = "EC_secp384r1" 97 | KeyAlgorithm_EC_secp521r1 KeyAlgorithm = "EC_secp521r1" 98 | KeyAlgorithm_RSA_1024 KeyAlgorithm = "RSA_1024" 99 | KeyAlgorithm_RSA_2048 KeyAlgorithm = "RSA_2048" 100 | KeyAlgorithm_RSA_3072 KeyAlgorithm = "RSA_3072" 101 | KeyAlgorithm_RSA_4096 KeyAlgorithm = "RSA_4096" 102 | ) 103 | 104 | type KeyUsageName string 105 | 106 | const ( 107 | KeyUsageName_ANY KeyUsageName = "ANY" 108 | KeyUsageName_CERTIFICATE_SIGNING KeyUsageName = "CERTIFICATE_SIGNING" 109 | KeyUsageName_CRL_SIGNING KeyUsageName = "CRL_SIGNING" 110 | KeyUsageName_CUSTOM KeyUsageName = "CUSTOM" 111 | KeyUsageName_DATA_ENCIPHERMENT KeyUsageName = "DATA_ENCIPHERMENT" 112 | KeyUsageName_DECIPHER_ONLY KeyUsageName = "DECIPHER_ONLY" 113 | KeyUsageName_DIGITAL_SIGNATURE KeyUsageName = "DIGITAL_SIGNATURE" 114 | KeyUsageName_ENCIPHER_ONLY KeyUsageName = "ENCIPHER_ONLY" 115 | KeyUsageName_KEY_AGREEMENT KeyUsageName = "KEY_AGREEMENT" 116 | KeyUsageName_KEY_ENCIPHERMENT KeyUsageName = "KEY_ENCIPHERMENT" 117 | KeyUsageName_NON_REPUDIATION KeyUsageName = "NON_REPUDIATION" 118 | ) 119 | 120 | type RecordType string 121 | 122 | const ( 123 | RecordType_CNAME RecordType = "CNAME" 124 | ) 125 | 126 | type RenewalEligibility string 127 | 128 | const ( 129 | RenewalEligibility_ELIGIBLE RenewalEligibility = "ELIGIBLE" 130 | RenewalEligibility_INELIGIBLE RenewalEligibility = "INELIGIBLE" 131 | ) 132 | 133 | type RenewalStatus string 134 | 135 | const ( 136 | RenewalStatus_FAILED RenewalStatus = "FAILED" 137 | RenewalStatus_PENDING_AUTO_RENEWAL RenewalStatus = "PENDING_AUTO_RENEWAL" 138 | RenewalStatus_PENDING_VALIDATION RenewalStatus = "PENDING_VALIDATION" 139 | RenewalStatus_SUCCESS RenewalStatus = "SUCCESS" 140 | ) 141 | 142 | type RevocationReason string 143 | 144 | const ( 145 | RevocationReason_AFFILIATION_CHANGED RevocationReason = "AFFILIATION_CHANGED" 146 | RevocationReason_A_A_COMPROMISE RevocationReason = "A_A_COMPROMISE" 147 | RevocationReason_CA_COMPROMISE RevocationReason = "CA_COMPROMISE" 148 | RevocationReason_CERTIFICATE_HOLD RevocationReason = "CERTIFICATE_HOLD" 149 | RevocationReason_CESSATION_OF_OPERATION RevocationReason = "CESSATION_OF_OPERATION" 150 | RevocationReason_KEY_COMPROMISE RevocationReason = "KEY_COMPROMISE" 151 | RevocationReason_PRIVILEGE_WITHDRAWN RevocationReason = "PRIVILEGE_WITHDRAWN" 152 | RevocationReason_REMOVE_FROM_CRL RevocationReason = "REMOVE_FROM_CRL" 153 | RevocationReason_SUPERCEDED RevocationReason = "SUPERCEDED" 154 | RevocationReason_UNSPECIFIED RevocationReason = "UNSPECIFIED" 155 | ) 156 | 157 | type SortBy string 158 | 159 | const ( 160 | SortBy_CREATED_AT SortBy = "CREATED_AT" 161 | ) 162 | 163 | type SortOrder string 164 | 165 | const ( 166 | SortOrder_ASCENDING SortOrder = "ASCENDING" 167 | SortOrder_DESCENDING SortOrder = "DESCENDING" 168 | ) 169 | 170 | type ValidationMethod string 171 | 172 | const ( 173 | ValidationMethod_DNS ValidationMethod = "DNS" 174 | ValidationMethod_EMAIL ValidationMethod = "EMAIL" 175 | ) 176 | -------------------------------------------------------------------------------- /helm/values.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft-07/schema#", 3 | "properties": { 4 | "image": { 5 | "description": "Container Image", 6 | "properties": { 7 | "repository": { 8 | "type": "string", 9 | "minLength": 1 10 | }, 11 | "tag": { 12 | "type": "string", 13 | "minLength": 1 14 | }, 15 | "pullPolicy": { 16 | "type": "string", 17 | "enum": ["IfNotPresent", "Always", "Never"] 18 | }, 19 | "pullSecrets": { 20 | "type": "array" 21 | } 22 | }, 23 | "required": [ 24 | "repository", 25 | "tag", 26 | "pullPolicy" 27 | ], 28 | "type": "object" 29 | }, 30 | "nameOverride": { 31 | "type": "string" 32 | }, 33 | "fullNameOverride": { 34 | "type": "string" 35 | }, 36 | "deployment": { 37 | "description": "Deployment settings", 38 | "properties": { 39 | "annotations": { 40 | "type": "object" 41 | }, 42 | "labels": { 43 | "type": "object" 44 | }, 45 | "containerPort": { 46 | "type": "integer", 47 | "minimum": 1, 48 | "maximum": 65535 49 | }, 50 | "replicas": { 51 | "type": "integer" 52 | }, 53 | "nodeSelector": { 54 | "type": "object" 55 | }, 56 | "tolerations": { 57 | "type": "array" 58 | }, 59 | "affinity": { 60 | "type": "object" 61 | }, 62 | "priorityClassName": { 63 | "type": "string" 64 | }, 65 | "extraVolumeMounts": { 66 | "type": "array" 67 | }, 68 | "extraVolumes": { 69 | "type": "array" 70 | }, 71 | "extraEnvVars": { 72 | "type": "array" 73 | } 74 | }, 75 | "required": [ 76 | "containerPort" 77 | ], 78 | "type": "object" 79 | }, 80 | "role": { 81 | "description": "Role settings", 82 | "properties": { 83 | "labels": { 84 | "type": "object" 85 | } 86 | } 87 | }, 88 | "metrics": { 89 | "description": "Metrics settings", 90 | "properties": { 91 | "service": { 92 | "description": "Kubernetes service settings", 93 | "properties": { 94 | "create": { 95 | "type": "boolean" 96 | }, 97 | "type": { 98 | "type": "string", 99 | "enum": ["ClusterIP", "NodePort", "LoadBalancer", "ExternalName"] 100 | } 101 | }, 102 | "required": [ 103 | "create", 104 | "type" 105 | ], 106 | "type": "object" 107 | } 108 | }, 109 | "required": [ 110 | "service" 111 | ], 112 | "type": "object" 113 | }, 114 | "resources": { 115 | "description": "Kubernetes resources settings", 116 | "properties": { 117 | "requests": { 118 | "description": "Kubernetes resource requests", 119 | "properties": { 120 | "memory": { 121 | "oneOf": [ 122 | { "type": "number" }, 123 | { "type": "string" } 124 | ] 125 | }, 126 | "cpu": { 127 | "oneOf": [ 128 | { "type": "number" }, 129 | { "type": "string" } 130 | ] 131 | } 132 | }, 133 | "required": [ 134 | "memory", 135 | "cpu" 136 | ], 137 | "type": "object" 138 | }, 139 | "limits": { 140 | "description": "Kubernetes resource limits", 141 | "properties": { 142 | "memory": { 143 | "oneOf": [ 144 | { "type": "number" }, 145 | { "type": "string" } 146 | ] 147 | }, 148 | "cpu": { 149 | "oneOf": [ 150 | { "type": "number" }, 151 | { "type": "string" } 152 | ] 153 | } 154 | }, 155 | "required": [ 156 | "memory", 157 | "cpu" 158 | ], 159 | "type": "object" 160 | } 161 | }, 162 | "required": [ 163 | "requests", 164 | "limits" 165 | ], 166 | "type": "object" 167 | }, 168 | "aws": { 169 | "description": "AWS API settings", 170 | "properties": { 171 | "region": { 172 | "type": "string" 173 | }, 174 | "endpoint_url": { 175 | "type": "string" 176 | }, 177 | "identity_endpoint_url": { 178 | "type": "string" 179 | }, 180 | "allow_unsafe_aws_endpoint_urls": { 181 | "type": "boolean", 182 | "default": false 183 | }, 184 | "credentials": { 185 | "description": "AWS credentials information", 186 | "properties": { 187 | "secretName": { 188 | "type": "string" 189 | }, 190 | "secretKey": { 191 | "type": "string" 192 | }, 193 | "profile": { 194 | "type": "string" 195 | } 196 | }, 197 | "type": "object" 198 | } 199 | }, 200 | "type": "object" 201 | }, 202 | "log": { 203 | "description": "Logging settings", 204 | "properties": { 205 | "enable_development_logging": { 206 | "type": "boolean" 207 | }, 208 | "level": { 209 | "type": "string" 210 | } 211 | }, 212 | "type": "object" 213 | }, 214 | "installScope": { 215 | "type": "string", 216 | "enum": ["cluster", "namespace"] 217 | }, 218 | "watchNamespace": { 219 | "type": "string" 220 | }, 221 | "watchSelectors": { 222 | "type": "string" 223 | }, 224 | "resourceTags": { 225 | "type": "array", 226 | "items": { 227 | "type": "string", 228 | "pattern": "(^$|^.*=.*$)" 229 | } 230 | }, 231 | "deletionPolicy": { 232 | "type": "string", 233 | "enum": ["delete", "retain"] 234 | }, 235 | "reconcile": { 236 | "description": "Reconcile settings. This is used to configure the controller's reconciliation behavior. e.g resyncPeriod and maxConcurrentSyncs", 237 | "properties": { 238 | "defaultResyncPeriod": { 239 | "type": "number" 240 | }, 241 | "resourceResyncPeriods": { 242 | "type": "object" 243 | }, 244 | "defaultMaxConcurentSyncs": { 245 | "type": "number" 246 | }, 247 | "resourceMaxConcurrentSyncs": { 248 | "type": "object" 249 | }, 250 | "resources": { 251 | "type": "array", 252 | "items": { 253 | "type": "string" 254 | }, 255 | "description": "List of resource kinds to reconcile. If empty, all resources will be reconciled.", 256 | "default": [] 257 | } 258 | }, 259 | "type": "object" 260 | }, 261 | "leaderElection": { 262 | "description": "Parameter to configure the controller's leader election system.", 263 | "properties": { 264 | "enabled": { 265 | "type": "boolean" 266 | }, 267 | "namespace": { 268 | "type": "string" 269 | } 270 | }, 271 | "type": "object" 272 | }, 273 | "enableCARM": { 274 | "description": "Parameter to enable or disable cross account resource management.", 275 | "type": "boolean", 276 | "default": true 277 | }, 278 | "serviceAccount": { 279 | "description": "ServiceAccount settings", 280 | "properties": { 281 | "create": { 282 | "type": "boolean" 283 | }, 284 | "name": { 285 | "type": "string" 286 | }, 287 | "annotations": { 288 | "type": "object" 289 | } 290 | }, 291 | "type": "object" 292 | } 293 | }, 294 | "featureGates": { 295 | "description": "Feature gates settings", 296 | "type": "object", 297 | "additionalProperties": { 298 | "type": "boolean" 299 | } 300 | }, 301 | "required": [ 302 | "image", 303 | "deployment", 304 | "metrics", 305 | "resources", 306 | "log", 307 | "installScope", 308 | "resourceTags", 309 | "serviceAccount" 310 | ], 311 | "title": "Values", 312 | "type": "object" 313 | } 314 | -------------------------------------------------------------------------------- /helm/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "ack-acm-controller.app.fullname" . }} 5 | namespace: {{ .Release.Namespace }} 6 | labels: 7 | app.kubernetes.io/name: {{ include "ack-acm-controller.app.name" . }} 8 | app.kubernetes.io/instance: {{ .Release.Name }} 9 | app.kubernetes.io/managed-by: Helm 10 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 11 | k8s-app: {{ include "ack-acm-controller.app.name" . }} 12 | helm.sh/chart: {{ include "ack-acm-controller.chart.name-version" . }} 13 | {{- range $key, $value := .Values.deployment.labels }} 14 | {{ $key }}: {{ $value | quote }} 15 | {{- end }} 16 | spec: 17 | replicas: {{ .Values.deployment.replicas }} 18 | selector: 19 | matchLabels: 20 | app.kubernetes.io/name: {{ include "ack-acm-controller.app.name" . }} 21 | app.kubernetes.io/instance: {{ .Release.Name }} 22 | template: 23 | metadata: 24 | {{- if .Values.deployment.annotations }} 25 | annotations: 26 | {{- range $key, $value := .Values.deployment.annotations }} 27 | {{ $key }}: {{ $value | quote }} 28 | {{- end }} 29 | {{- end }} 30 | labels: 31 | app.kubernetes.io/name: {{ include "ack-acm-controller.app.name" . }} 32 | app.kubernetes.io/instance: {{ .Release.Name }} 33 | app.kubernetes.io/managed-by: Helm 34 | k8s-app: {{ include "ack-acm-controller.app.name" . }} 35 | {{- range $key, $value := .Values.deployment.labels }} 36 | {{ $key }}: {{ $value | quote }} 37 | {{- end }} 38 | spec: 39 | serviceAccountName: {{ include "ack-acm-controller.service-account.name" . }} 40 | {{- if .Values.image.pullSecrets }} 41 | imagePullSecrets: 42 | {{- range .Values.image.pullSecrets }} 43 | - name: {{ . }} 44 | {{- end }} 45 | {{- end }} 46 | containers: 47 | - command: 48 | - ./bin/controller 49 | args: 50 | - --aws-region 51 | - "$(AWS_REGION)" 52 | - --aws-endpoint-url 53 | - "$(AWS_ENDPOINT_URL)" 54 | {{- if .Values.aws.identity_endpoint_url }} 55 | - --aws-identity-endpoint-url 56 | - "$(AWS_IDENTITY_ENDPOINT_URL)" 57 | {{- end }} 58 | {{- if .Values.aws.allow_unsafe_aws_endpoint_urls }} 59 | - --allow-unsafe-aws-endpoint-urls 60 | {{- end }} 61 | {{- if .Values.log.enable_development_logging }} 62 | - --enable-development-logging 63 | {{- end }} 64 | - --log-level 65 | - "$(ACK_LOG_LEVEL)" 66 | - --resource-tags 67 | - "$(ACK_RESOURCE_TAGS)" 68 | - --watch-namespace 69 | - "$(ACK_WATCH_NAMESPACE)" 70 | - --watch-selectors 71 | - "$(ACK_WATCH_SELECTORS)" 72 | - --reconcile-resources 73 | - "$(RECONCILE_RESOURCES)" 74 | - --deletion-policy 75 | - "$(DELETION_POLICY)" 76 | {{- if .Values.leaderElection.enabled }} 77 | - --enable-leader-election 78 | - --leader-election-namespace 79 | - "$(LEADER_ELECTION_NAMESPACE)" 80 | {{- end }} 81 | {{- if gt (int .Values.reconcile.defaultResyncPeriod) 0 }} 82 | - --reconcile-default-resync-seconds 83 | - "$(RECONCILE_DEFAULT_RESYNC_SECONDS)" 84 | {{- end }} 85 | {{- range $key, $value := .Values.reconcile.resourceResyncPeriods }} 86 | - --reconcile-resource-resync-seconds 87 | - "$(RECONCILE_RESOURCE_RESYNC_SECONDS_{{ $key | upper }})" 88 | {{- end }} 89 | {{- if gt (int .Values.reconcile.defaultMaxConcurrentSyncs) 0 }} 90 | - --reconcile-default-max-concurrent-syncs 91 | - "$(RECONCILE_DEFAULT_MAX_CONCURRENT_SYNCS)" 92 | {{- end }} 93 | {{- range $key, $value := .Values.reconcile.resourceMaxConcurrentSyncs }} 94 | - --reconcile-resource-max-concurrent-syncs 95 | - "$(RECONCILE_RESOURCE_MAX_CONCURRENT_SYNCS_{{ $key | upper }})" 96 | {{- end }} 97 | {{- if .Values.featureGates}} 98 | - --feature-gates 99 | - "$(FEATURE_GATES)" 100 | {{- end }} 101 | - --enable-carm={{ .Values.enableCARM }} 102 | image: {{ .Values.image.repository }}:{{ .Values.image.tag }} 103 | imagePullPolicy: {{ .Values.image.pullPolicy }} 104 | name: controller 105 | ports: 106 | - name: http 107 | containerPort: {{ .Values.deployment.containerPort }} 108 | resources: 109 | {{- toYaml .Values.resources | nindent 10 }} 110 | env: 111 | - name: ACK_SYSTEM_NAMESPACE 112 | valueFrom: 113 | fieldRef: 114 | fieldPath: metadata.namespace 115 | - name: AWS_REGION 116 | value: {{ .Values.aws.region }} 117 | - name: AWS_ENDPOINT_URL 118 | value: {{ .Values.aws.endpoint_url | quote }} 119 | - name: AWS_IDENTITY_ENDPOINT_URL 120 | value: {{ .Values.aws.identity_endpoint_url | quote }} 121 | - name: ACK_WATCH_NAMESPACE 122 | value: {{ include "ack-acm-controller.watch-namespace" . }} 123 | - name: ACK_WATCH_SELECTORS 124 | value: {{ .Values.watchSelectors }} 125 | - name: RECONCILE_RESOURCES 126 | value: {{ join "," .Values.reconcile.resources | quote }} 127 | - name: DELETION_POLICY 128 | value: {{ .Values.deletionPolicy }} 129 | - name: LEADER_ELECTION_NAMESPACE 130 | value: {{ .Values.leaderElection.namespace | quote }} 131 | - name: ACK_LOG_LEVEL 132 | value: {{ .Values.log.level | quote }} 133 | - name: ACK_RESOURCE_TAGS 134 | value: {{ join "," .Values.resourceTags | quote }} 135 | {{- if gt (int .Values.reconcile.defaultResyncPeriod) 0 }} 136 | - name: RECONCILE_DEFAULT_RESYNC_SECONDS 137 | value: {{ .Values.reconcile.defaultResyncPeriod | quote }} 138 | {{- end }} 139 | {{- range $key, $value := .Values.reconcile.resourceResyncPeriods }} 140 | - name: RECONCILE_RESOURCE_RESYNC_SECONDS_{{ $key | upper }} 141 | value: {{ $key }}={{ $value }} 142 | {{- end }} 143 | {{- if gt (int .Values.reconcile.defaultMaxConcurrentSyncs) 0 }} 144 | - name: RECONCILE_DEFAULT_MAX_CONCURRENT_SYNCS 145 | value: {{ .Values.reconcile.defaultMaxConcurrentSyncs | quote }} 146 | {{- end }} 147 | {{- range $key, $value := .Values.reconcile.resourceMaxConcurrentSyncs }} 148 | - name: RECONCILE_RESOURCE_MAX_CONCURRENT_SYNCS_{{ $key | upper }} 149 | value: {{ $key }}={{ $value }} 150 | {{- end }} 151 | {{- if .Values.featureGates}} 152 | - name: FEATURE_GATES 153 | value: {{ include "ack-acm-controller.feature-gates" . }} 154 | {{- end }} 155 | {{- if .Values.aws.credentials.secretName }} 156 | - name: AWS_SHARED_CREDENTIALS_FILE 157 | value: {{ include "ack-acm-controller.aws.credentials.path" . }} 158 | - name: AWS_PROFILE 159 | value: {{ .Values.aws.credentials.profile }} 160 | {{- end }} 161 | {{- if .Values.deployment.extraEnvVars -}} 162 | {{ toYaml .Values.deployment.extraEnvVars | nindent 8 }} 163 | {{- end }} 164 | {{- if or .Values.aws.credentials.secretName .Values.deployment.extraVolumeMounts }} 165 | volumeMounts: 166 | {{- if .Values.aws.credentials.secretName }} 167 | - name: {{ .Values.aws.credentials.secretName }} 168 | mountPath: {{ include "ack-acm-controller.aws.credentials.secret_mount_path" . }} 169 | readOnly: true 170 | {{- end }} 171 | {{- if .Values.deployment.extraVolumeMounts -}} 172 | {{ toYaml .Values.deployment.extraVolumeMounts | nindent 10 }} 173 | {{- end }} 174 | {{- end }} 175 | securityContext: 176 | allowPrivilegeEscalation: false 177 | privileged: false 178 | readOnlyRootFilesystem: true 179 | runAsNonRoot: true 180 | capabilities: 181 | drop: 182 | - ALL 183 | livenessProbe: 184 | httpGet: 185 | path: /healthz 186 | port: 8081 187 | initialDelaySeconds: 15 188 | periodSeconds: 20 189 | readinessProbe: 190 | httpGet: 191 | path: /readyz 192 | port: 8081 193 | initialDelaySeconds: 5 194 | periodSeconds: 10 195 | securityContext: 196 | seccompProfile: 197 | type: RuntimeDefault 198 | terminationGracePeriodSeconds: 10 199 | nodeSelector: {{ toYaml .Values.deployment.nodeSelector | nindent 8 }} 200 | {{ if .Values.deployment.tolerations -}} 201 | tolerations: {{ toYaml .Values.deployment.tolerations | nindent 8 }} 202 | {{ end -}} 203 | {{ if .Values.deployment.affinity -}} 204 | affinity: {{ toYaml .Values.deployment.affinity | nindent 8 }} 205 | {{ end -}} 206 | {{ if .Values.deployment.priorityClassName -}} 207 | priorityClassName: {{ .Values.deployment.priorityClassName }} 208 | {{ end -}} 209 | hostIPC: false 210 | hostPID: false 211 | hostNetwork: {{ .Values.deployment.hostNetwork }} 212 | dnsPolicy: {{ .Values.deployment.dnsPolicy }} 213 | {{- if or .Values.aws.credentials.secretName .Values.deployment.extraVolumes }} 214 | volumes: 215 | {{- if .Values.aws.credentials.secretName }} 216 | - name: {{ .Values.aws.credentials.secretName }} 217 | secret: 218 | secretName: {{ .Values.aws.credentials.secretName }} 219 | {{- end }} 220 | {{- if .Values.deployment.extraVolumes }} 221 | {{- toYaml .Values.deployment.extraVolumes | nindent 8 }} 222 | {{- end }} 223 | {{- end }} 224 | {{- with .Values.deployment.strategy }} 225 | strategy: {{- toYaml . | nindent 4 }} 226 | {{- end }} 227 | -------------------------------------------------------------------------------- /apis/v1alpha1/types.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package v1alpha1 17 | 18 | import ( 19 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 20 | "github.com/aws/aws-sdk-go/aws" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | // Hack to avoid import errors during build... 25 | var ( 26 | _ = &metav1.Time{} 27 | _ = &aws.JSONValue{} 28 | _ = ackv1alpha1.AWSAccountID("") 29 | ) 30 | 31 | // Contains metadata about an ACM certificate. This structure is returned in 32 | // the response to a DescribeCertificate request. 33 | type CertificateDetail struct { 34 | CertificateARN *string `json:"certificateARN,omitempty"` 35 | CertificateAuthorityARN *string `json:"certificateAuthorityARN,omitempty"` 36 | CreatedAt *metav1.Time `json:"createdAt,omitempty"` 37 | DomainName *string `json:"domainName,omitempty"` 38 | DomainValidationOptions []*DomainValidation `json:"domainValidationOptions,omitempty"` 39 | ExtendedKeyUsages []*ExtendedKeyUsage `json:"extendedKeyUsages,omitempty"` 40 | FailureReason *string `json:"failureReason,omitempty"` 41 | ImportedAt *metav1.Time `json:"importedAt,omitempty"` 42 | InUseBy []*string `json:"inUseBy,omitempty"` 43 | IssuedAt *metav1.Time `json:"issuedAt,omitempty"` 44 | Issuer *string `json:"issuer,omitempty"` 45 | KeyAlgorithm *string `json:"keyAlgorithm,omitempty"` 46 | KeyUsages []*KeyUsage `json:"keyUsages,omitempty"` 47 | NotAfter *metav1.Time `json:"notAfter,omitempty"` 48 | NotBefore *metav1.Time `json:"notBefore,omitempty"` 49 | // Structure that contains options for your certificate. Currently, you can 50 | // use this only to specify whether to opt in to or out of certificate transparency 51 | // logging. Some browsers require that public certificates issued for your domain 52 | // be recorded in a log. Certificates that are not logged typically generate 53 | // a browser error. Transparency makes it possible for you to detect SSL/TLS 54 | // certificates that have been mistakenly or maliciously issued for your domain. 55 | // For general information, see Certificate Transparency Logging (https://docs.aws.amazon.com/acm/latest/userguide/acm-concepts.html#concept-transparency). 56 | Options *CertificateOptions `json:"options,omitempty"` 57 | RenewalEligibility *string `json:"renewalEligibility,omitempty"` 58 | // Contains information about the status of ACM's managed renewal (https://docs.aws.amazon.com/acm/latest/userguide/acm-renewal.html) 59 | // for the certificate. This structure exists only when the certificate type 60 | // is AMAZON_ISSUED. 61 | RenewalSummary *RenewalSummary `json:"renewalSummary,omitempty"` 62 | RevocationReason *string `json:"revocationReason,omitempty"` 63 | RevokedAt *metav1.Time `json:"revokedAt,omitempty"` 64 | Serial *string `json:"serial,omitempty"` 65 | SignatureAlgorithm *string `json:"signatureAlgorithm,omitempty"` 66 | Status *string `json:"status,omitempty"` 67 | Subject *string `json:"subject,omitempty"` 68 | SubjectAlternativeNames []*string `json:"subjectAlternativeNames,omitempty"` 69 | Type *string `json:"type_,omitempty"` 70 | } 71 | 72 | // Structure that contains options for your certificate. Currently, you can 73 | // use this only to specify whether to opt in to or out of certificate transparency 74 | // logging. Some browsers require that public certificates issued for your domain 75 | // be recorded in a log. Certificates that are not logged typically generate 76 | // a browser error. Transparency makes it possible for you to detect SSL/TLS 77 | // certificates that have been mistakenly or maliciously issued for your domain. 78 | // For general information, see Certificate Transparency Logging (https://docs.aws.amazon.com/acm/latest/userguide/acm-concepts.html#concept-transparency). 79 | type CertificateOptions struct { 80 | CertificateTransparencyLoggingPreference *string `json:"certificateTransparencyLoggingPreference,omitempty"` 81 | } 82 | 83 | // This structure is returned in the response object of ListCertificates action. 84 | type CertificateSummary struct { 85 | CertificateARN *string `json:"certificateARN,omitempty"` 86 | CreatedAt *metav1.Time `json:"createdAt,omitempty"` 87 | DomainName *string `json:"domainName,omitempty"` 88 | Exported *bool `json:"exported,omitempty"` 89 | ExtendedKeyUsages []*string `json:"extendedKeyUsages,omitempty"` 90 | HasAdditionalSubjectAlternativeNames *bool `json:"hasAdditionalSubjectAlternativeNames,omitempty"` 91 | ImportedAt *metav1.Time `json:"importedAt,omitempty"` 92 | InUse *bool `json:"inUse,omitempty"` 93 | IssuedAt *metav1.Time `json:"issuedAt,omitempty"` 94 | KeyAlgorithm *string `json:"keyAlgorithm,omitempty"` 95 | KeyUsages []*string `json:"keyUsages,omitempty"` 96 | NotAfter *metav1.Time `json:"notAfter,omitempty"` 97 | NotBefore *metav1.Time `json:"notBefore,omitempty"` 98 | RenewalEligibility *string `json:"renewalEligibility,omitempty"` 99 | RevokedAt *metav1.Time `json:"revokedAt,omitempty"` 100 | Status *string `json:"status,omitempty"` 101 | SubjectAlternativeNameSummaries []*string `json:"subjectAlternativeNameSummaries,omitempty"` 102 | Type *string `json:"type_,omitempty"` 103 | } 104 | 105 | // Contains information about the validation of each domain name in the certificate. 106 | type DomainValidation struct { 107 | DomainName *string `json:"domainName,omitempty"` 108 | // Contains a DNS record value that you can use to validate ownership or control 109 | // of a domain. This is used by the DescribeCertificate action. 110 | ResourceRecord *ResourceRecord `json:"resourceRecord,omitempty"` 111 | ValidationDomain *string `json:"validationDomain,omitempty"` 112 | ValidationEmails []*string `json:"validationEmails,omitempty"` 113 | ValidationMethod *string `json:"validationMethod,omitempty"` 114 | ValidationStatus *string `json:"validationStatus,omitempty"` 115 | } 116 | 117 | // Contains information about the domain names that you want ACM to use to send 118 | // you emails that enable you to validate domain ownership. 119 | type DomainValidationOption struct { 120 | DomainName *string `json:"domainName,omitempty"` 121 | ValidationDomain *string `json:"validationDomain,omitempty"` 122 | } 123 | 124 | // The Extended Key Usage X.509 v3 extension defines one or more purposes for 125 | // which the public key can be used. This is in addition to or in place of the 126 | // basic purposes specified by the Key Usage extension. 127 | type ExtendedKeyUsage struct { 128 | Name *string `json:"name,omitempty"` 129 | OID *string `json:"oid,omitempty"` 130 | } 131 | 132 | // This structure can be used in the ListCertificates action to filter the output 133 | // of the certificate list. 134 | type Filters struct { 135 | ExtendedKeyUsage []*string `json:"extendedKeyUsage,omitempty"` 136 | KeyTypes []*string `json:"keyTypes,omitempty"` 137 | KeyUsage []*string `json:"keyUsage,omitempty"` 138 | } 139 | 140 | // The Key Usage X.509 v3 extension defines the purpose of the public key contained 141 | // in the certificate. 142 | type KeyUsage struct { 143 | Name *string `json:"name,omitempty"` 144 | } 145 | 146 | // Contains information about the status of ACM's managed renewal (https://docs.aws.amazon.com/acm/latest/userguide/acm-renewal.html) 147 | // for the certificate. This structure exists only when the certificate type 148 | // is AMAZON_ISSUED. 149 | type RenewalSummary struct { 150 | DomainValidationOptions []*DomainValidation `json:"domainValidationOptions,omitempty"` 151 | RenewalStatus *string `json:"renewalStatus,omitempty"` 152 | RenewalStatusReason *string `json:"renewalStatusReason,omitempty"` 153 | UpdatedAt *metav1.Time `json:"updatedAt,omitempty"` 154 | } 155 | 156 | // Contains a DNS record value that you can use to validate ownership or control 157 | // of a domain. This is used by the DescribeCertificate action. 158 | type ResourceRecord struct { 159 | Name *string `json:"name,omitempty"` 160 | Type *string `json:"type_,omitempty"` 161 | Value *string `json:"value,omitempty"` 162 | } 163 | 164 | // A key-value pair that identifies or specifies metadata about an ACM resource. 165 | type Tag struct { 166 | Key *string `json:"key,omitempty"` 167 | Value *string `json:"value,omitempty"` 168 | } 169 | -------------------------------------------------------------------------------- /pkg/resource/certificate/hooks.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package certificate 15 | 16 | import ( 17 | "context" 18 | "crypto/ecdsa" 19 | "crypto/rand" 20 | "crypto/rsa" 21 | "crypto/x509" 22 | "encoding/pem" 23 | "errors" 24 | "fmt" 25 | "io" 26 | "strings" 27 | 28 | "github.com/aws-controllers-k8s/acm-controller/pkg/tags" 29 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 30 | ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" 31 | ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" 32 | ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" 33 | svcsdk "github.com/aws/aws-sdk-go-v2/service/acm" 34 | pkcs8 "github.com/youmark/pkcs8" 35 | ) 36 | 37 | const ( 38 | // DNS validation only works for up to 5 chained CNAME records 39 | limitDomainValidationOptionsPublic = 5 40 | ) 41 | 42 | var ( 43 | errTooManyDomainValidationOptions = errors.New( 44 | "Too many domain validation errors", 45 | ) 46 | 47 | domainValidationOptionsExceededMsg = fmt.Sprintf( 48 | "Certificate cannot have more than %d domain validation options "+ 49 | "when requesting a public certificate", 50 | limitDomainValidationOptionsPublic, 51 | ) 52 | ) 53 | 54 | // validatePublicValidationOptions checks that when requesting a public 55 | // certificate, we do not exceed the number of additional CNAME records that 56 | // DNS verification can handle. 57 | func validatePublicValidationOptions( 58 | r *resource, 59 | ) error { 60 | // If the certificateAuthorityARN field is empty, that means this is a 61 | // request for a public certificate. If so, because we require DNS 62 | // verification for public certificates (due to email verification not be 63 | // automateable), we need to limit the number of chained CNAME records in 64 | // the DomainValidationOptions field to 5, since DNS verification only 65 | // works on up to 5 subdomains. 66 | if r.ko.Spec.CertificateAuthorityARN != nil { 67 | numDVOptions := len(r.ko.Spec.DomainValidationOptions) 68 | if numDVOptions > limitDomainValidationOptionsPublic { 69 | return errTooManyDomainValidationOptions 70 | } 71 | } 72 | return nil 73 | } 74 | 75 | // maybeImportCertificate imports a certificate into ACM if Spec.Certificate is set. 76 | func (rm *resourceManager) maybeImportCertificate(ctx context.Context, r *resource) (*resource, bool, error) { 77 | certSpec := r.ko.Spec 78 | if certSpec.Certificate != nil { 79 | if certSpec.DomainName != nil || len(certSpec.DomainValidationOptions) > 0 || certSpec.KeyAlgorithm != nil || 80 | len(certSpec.SubjectAlternativeNames) > 0 || certSpec.Options != nil { 81 | return nil, false, ackerr.NewTerminalError(errors.New("cannot set fields used for requesting a certificate when importing a certificate")) 82 | } 83 | input, err := rm.newImportCertificateInput(ctx, r) 84 | if err != nil { 85 | return nil, false, err 86 | } 87 | if len(input.PrivateKey) == 0 { 88 | return nil, false, ackerr.NewTerminalError(errors.New("privateKey is required when importing a certificate")) 89 | } 90 | created, err := rm.importCertificate(ctx, r, input) 91 | if err != nil { 92 | return nil, false, err 93 | } 94 | return created, true, nil 95 | } 96 | if certSpec.DomainName != nil && (certSpec.Certificate != nil || certSpec.PrivateKey != nil || certSpec.CertificateChain != nil) { 97 | return nil, false, ackerr.NewTerminalError(errors.New("cannot set fields used for importing a certificate when requesting a certificate")) 98 | } 99 | return nil, false, nil 100 | } 101 | 102 | var ( 103 | syncTags = tags.SyncTags 104 | listTags = tags.ListTags 105 | ) 106 | 107 | // importCertificate imports a certificate into ACM. 108 | func (rm *resourceManager) importCertificate( 109 | ctx context.Context, 110 | desired *resource, 111 | input *svcsdk.ImportCertificateInput, 112 | ) (created *resource, err error) { 113 | rlog := ackrtlog.FromContext(ctx) 114 | exit := rlog.Trace("rm.importCertificate") 115 | defer func(err error) { exit(err) }(err) 116 | 117 | resp, respErr := rm.sdkapi.ImportCertificate(ctx, input) 118 | rm.metrics.RecordAPICall("CREATE", "ImportCertificate", respErr) 119 | if respErr != nil { 120 | return nil, respErr 121 | } 122 | // Merge in the information we read from the API call above to the copy of 123 | // the original Kubernetes object we passed to the function 124 | ko := desired.ko.DeepCopy() 125 | created = &resource{ko} 126 | rm.setResourceFromImportCertificateOutput(created, resp) 127 | rm.setStatusDefaults(ko) 128 | return created, nil 129 | } 130 | 131 | // importCertificateInput exists as a workaround for a limitation in code-generator. 132 | // code-generator does not resolve secret key references for custom []byte fields like PrivateKey and Certificate. 133 | type importCertificateInput struct { 134 | Certificate *ackv1alpha1.SecretKeyReference 135 | CertificateChain *ackv1alpha1.SecretKeyReference 136 | PrivateKey *ackv1alpha1.SecretKeyReference 137 | *svcsdk.ImportCertificateInput 138 | } 139 | 140 | // generateRandomString generates a cryptographically secure random string of a given length 141 | // using a specified character set. 142 | func generateRandomString(length int) (string, error) { 143 | const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;:,.<>?" 144 | b := make([]byte, length) 145 | if _, err := io.ReadFull(rand.Reader, b); err != nil { 146 | return "", err 147 | } 148 | 149 | result := make([]byte, length) 150 | charsetLen := len(charset) 151 | for i := 0; i < length; i++ { 152 | result[i] = charset[int(b[i])%charsetLen] 153 | } 154 | 155 | return string(result), nil 156 | } 157 | 158 | func (rm *resourceManager) exportCertificate( 159 | ctx context.Context, 160 | r *resource, 161 | ) error { 162 | if r.ko.Spec.ExportTo == nil { 163 | return nil 164 | } 165 | 166 | input := &svcsdk.ExportCertificateInput{} 167 | if r.ko.Status.ACKResourceMetadata != nil && r.ko.Status.ACKResourceMetadata.ARN != nil { 168 | input.CertificateArn = (*string)(r.ko.Status.ACKResourceMetadata.ARN) 169 | } 170 | 171 | passphraseLength := 8 // Desired length of the passphrase 172 | passphrase, err := generateRandomString(passphraseLength) 173 | if err != nil { 174 | return err 175 | } 176 | input.Passphrase = []byte(passphrase) 177 | 178 | resp, err := rm.sdkapi.ExportCertificate(ctx, input) 179 | rm.metrics.RecordAPICall("READ_ONE", "ExportCertificate", err) 180 | if err != nil { 181 | return err 182 | } 183 | 184 | certificateChain := *resp.Certificate 185 | if resp.CertificateChain != nil && *resp.CertificateChain != "" { 186 | certificateChain = certificateChain + *resp.CertificateChain 187 | } 188 | 189 | if r.ko.Spec.ExportTo.Namespace != "" { 190 | if err := rm.rr.WriteToSecret(ctx, certificateChain, r.ko.Spec.ExportTo.Namespace, r.ko.Spec.ExportTo.Name, r.ko.Spec.ExportTo.Key); err != nil { 191 | return err 192 | } 193 | } else { 194 | if err := rm.rr.WriteToSecret(ctx, certificateChain, r.ko.Namespace, r.ko.Spec.ExportTo.Name, r.ko.Spec.ExportTo.Key); err != nil { 195 | return err 196 | } 197 | } 198 | 199 | decryptedKey, err := DecryptPrivateKey([]byte(*resp.PrivateKey), []byte(passphrase), *r.ko.Spec.KeyAlgorithm) 200 | if err != nil { 201 | return err 202 | } 203 | 204 | if r.ko.Spec.ExportTo.Namespace != "" { 205 | if err := rm.rr.WriteToSecret(ctx, string(decryptedKey), r.ko.Spec.ExportTo.Namespace, r.ko.Spec.ExportTo.Name, "tls.key"); err != nil { 206 | return err 207 | } 208 | } else { 209 | if err := rm.rr.WriteToSecret(ctx, string(decryptedKey), r.ko.Namespace, r.ko.Spec.ExportTo.Name, "tls.key"); err != nil { 210 | return err 211 | } 212 | } 213 | 214 | // No need to update secret annotations since we're now tracking IssuedAt changes 215 | // in the template logic using the Certificate object's Status field 216 | return nil 217 | } 218 | 219 | func DecryptPrivateKey(encryptedPEM, passphrase []byte, keyAlgorithm string) ([]byte, error) { 220 | pemBlock, _ := pem.Decode(encryptedPEM) 221 | if pemBlock == nil { 222 | return nil, errors.New("failed to decode PEM block: no PEM data found") 223 | } 224 | privateKey, err := pkcs8.ParsePKCS8PrivateKey(pemBlock.Bytes, passphrase) 225 | if err != nil { 226 | return nil, errors.New("failed to decrypt PEM block") 227 | } 228 | 229 | // NOTE: Algorithms supported for an ACM certificate request include: RSA_2048, EC_prime256v1, EC_secp384r1 230 | if strings.Contains(keyAlgorithm, "RSA") { 231 | derBytes, err := x509.MarshalPKCS8PrivateKey(privateKey.(*rsa.PrivateKey)) 232 | if err != nil { 233 | return nil, errors.New("failed to marshal PEM block") 234 | } 235 | 236 | pemBytes := pem.EncodeToMemory(&pem.Block{ 237 | Type: "PRIVATE KEY", 238 | Bytes: derBytes, 239 | }) 240 | return pemBytes, err 241 | } else { 242 | derBytes, err := x509.MarshalPKCS8PrivateKey(privateKey.(*ecdsa.PrivateKey)) 243 | if err != nil { 244 | return nil, errors.New("failed to marshal PEM block") 245 | } 246 | 247 | pemBytes := pem.EncodeToMemory(&pem.Block{ 248 | Type: "PRIVATE KEY", 249 | Bytes: derBytes, 250 | }) 251 | return pemBytes, err 252 | } 253 | } 254 | 255 | func compareCertificateIssuedAt( 256 | delta *ackcompare.Delta, 257 | a *resource, 258 | b *resource, 259 | ) { 260 | if a.ko.Spec.ExportTo != nil { 261 | // NOTE: first time the certificate is issued 262 | if a.ko.Status.IssuedAt == nil && b.ko.Status.Status != nil && *b.ko.Status.Status == "ISSUED" { 263 | // NOTE: ack runtime ONLY goes into update if delta key starts with "Spec" 264 | // https://github.com/aws-controllers-k8s/runtime/blob/main/pkg/runtime/reconciler.go#L894-L903 265 | delta.Add("Spec.Status.IssuedAt", a.ko.Status.IssuedAt, b.ko.Status.IssuedAt) 266 | } 267 | // NOTE: when the certificate is renewed 268 | if a.ko.Status.Serial != nil && b.ko.Status.Serial != nil && *a.ko.Status.Serial != *b.ko.Status.Serial { 269 | // NOTE: ack runtime ONLY goes into update if delta key starts with "Spec" 270 | // https://github.com/aws-controllers-k8s/runtime/blob/main/pkg/runtime/reconciler.go#L894-L903 271 | delta.Add("Spec.Status.Serial", a.ko.Status.Serial, b.ko.Status.Serial) 272 | } 273 | } 274 | } 275 | --------------------------------------------------------------------------------