Skip to content

Commit 2aa9526

Browse files
authored
Merge pull request #4 from projectsyn/initial-implementation
Initial implementation
2 parents aeca5f8 + 6a91a2d commit 2aa9526

36 files changed

+4082
-50
lines changed

.codeclimate.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,25 @@ plugins:
1010
enabled: true
1111
fixme:
1212
enabled: true
13+
14+
checks:
15+
argument-count:
16+
enabled: true
17+
config:
18+
threshold: 6
19+
method-lines:
20+
enabled: true
21+
config:
22+
threshold: 50
23+
return-statements:
24+
enabled: true
25+
config:
26+
threshold: 10
27+
similar-code:
28+
enabled: true
29+
identical-code:
30+
enabled: true
31+
1332
exclude_patterns:
1433
- 'config/'
1534
- 'db/'

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ build: build-bin build-docker ## All-in-one build
2323
.PHONY: build-bin
2424
build-bin: export CGO_ENABLED = 0
2525
build-bin: fmt vet ## Build binary
26-
@go build -o $(BIN_FILENAME) ./...
26+
@go build -o $(BIN_FILENAME)
27+
28+
.PHONY: run
29+
run:
30+
go run . -zap-devel -zap-log-level info
2731

2832
.PHONY: build-docker
2933
build-docker: build-bin ## Build docker image

PROJECT

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
domain: syn.tools
2+
layout:
3+
- go.kubebuilder.io/v3
4+
projectName: k8s-service-ca-controller
5+
repo: github.com/projectsyn/k8s-service-ca-controller
6+
resources:
7+
- controller: true
8+
kind: Service
9+
version: v1
10+
- controller: true
11+
kind: ConfigMap
12+
version: v1
13+
version: "3"

certs/ca.go

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package certs
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"unicode/utf8"
7+
8+
cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
9+
cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
10+
"github.com/go-logr/logr"
11+
corev1 "k8s.io/api/core/v1"
12+
extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
13+
"k8s.io/apimachinery/pkg/api/errors"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
16+
)
17+
18+
const (
19+
SelfSignedIssuerName = "service-ca-self-signed"
20+
CACertName = "service-ca-certificate"
21+
CAName = "service-ca"
22+
CASecretName = "service-ca-root"
23+
ServiceIssuerName = "service-ca-issuer"
24+
)
25+
26+
// ensureCA ensures that the Service CA is completely setup on the cluster
27+
func ensureCA(ctx context.Context, c client.Client, l logr.Logger, caNamespace string) error {
28+
log := l.WithValues("caNamespace", caNamespace)
29+
30+
err := ensureSelfSignedIssuer(ctx, c, log, caNamespace)
31+
if err != nil {
32+
return err
33+
}
34+
35+
err = ensureCACertificate(ctx, c, log, caNamespace)
36+
if err != nil {
37+
return err
38+
}
39+
40+
err = ensureServiceCAIssuer(ctx, c, log, caNamespace)
41+
if err != nil {
42+
return err
43+
}
44+
45+
return nil
46+
}
47+
48+
// ensureSelfSignedIssuer creates a self-signed issuer in `caNamespace` if it
49+
// doesn't exist
50+
func ensureSelfSignedIssuer(ctx context.Context, c client.Client, l logr.Logger, caNamespace string) error {
51+
iss := cmapi.Issuer{}
52+
err := c.Get(ctx, client.ObjectKey{
53+
Name: SelfSignedIssuerName,
54+
Namespace: caNamespace,
55+
}, &iss)
56+
if err != nil && !errors.IsNotFound(err) {
57+
l.Error(err, "while fetching self-signed issuer")
58+
return err
59+
}
60+
if errors.IsNotFound(err) {
61+
l.Info("Self-signed issuer doesn't exist, creating...")
62+
iss.Name = SelfSignedIssuerName
63+
iss.Namespace = caNamespace
64+
iss.Spec.SelfSigned = &cmapi.SelfSignedIssuer{}
65+
if err := c.Create(ctx, &iss); err != nil {
66+
return err
67+
}
68+
}
69+
return nil
70+
}
71+
72+
// ensureCACertificate creates the Service CA certificate if it doesn't exist
73+
func ensureCACertificate(ctx context.Context, c client.Client, l logr.Logger, caNamespace string) error {
74+
// Create CA cert if not exists (in caNamespace)
75+
caCert := cmapi.Certificate{}
76+
err := c.Get(ctx, client.ObjectKey{
77+
Name: CACertName,
78+
Namespace: caNamespace,
79+
}, &caCert)
80+
if err != nil && !errors.IsNotFound(err) {
81+
l.Error(err, "while fetching service CA certificate")
82+
return err
83+
}
84+
if errors.IsNotFound(err) {
85+
l.Info("Service CA certificate doesn't exist, creating...")
86+
caCert = newCACertificate(caNamespace)
87+
if err := c.Create(ctx, &caCert); err != nil {
88+
return err
89+
}
90+
}
91+
return nil
92+
}
93+
94+
// ensureServiceCAIssuer creates the ClusterIssuer for the Service CA if it
95+
// doesn't exist
96+
func ensureServiceCAIssuer(ctx context.Context, c client.Client, l logr.Logger, caNamespace string) error {
97+
// Create Service CA clusterissuer, if not exists
98+
serviceIssuer := cmapi.ClusterIssuer{}
99+
err := c.Get(ctx, client.ObjectKey{Name: ServiceIssuerName}, &serviceIssuer)
100+
if err != nil && !errors.IsNotFound(err) {
101+
l.Error(err, "while fetching service CA cluster issuer")
102+
return err
103+
}
104+
if errors.IsNotFound(err) {
105+
l.Info("Service CA cluster issuer doesn't exist, creating...")
106+
serviceIssuer.Name = ServiceIssuerName
107+
serviceIssuer.Spec.CA = &cmapi.CAIssuer{
108+
SecretName: CASecretName,
109+
}
110+
if err := c.Create(ctx, &serviceIssuer); err != nil {
111+
return err
112+
}
113+
}
114+
return nil
115+
}
116+
117+
// newCACertificate returns a new Service CA certificate resource
118+
func newCACertificate(caNamespace string) cmapi.Certificate {
119+
return cmapi.Certificate{
120+
ObjectMeta: metav1.ObjectMeta{
121+
Name: CACertName,
122+
Namespace: caNamespace,
123+
},
124+
Spec: cmapi.CertificateSpec{
125+
IsCA: true,
126+
CommonName: CAName,
127+
SecretName: CASecretName,
128+
// TODO: make the private key config configurable?
129+
PrivateKey: &cmapi.CertificatePrivateKey{
130+
Algorithm: cmapi.ECDSAKeyAlgorithm,
131+
Size: 521,
132+
},
133+
IssuerRef: cmmeta.ObjectReference{
134+
Name: SelfSignedIssuerName,
135+
Kind: "Issuer",
136+
Group: "cert-manager.io",
137+
},
138+
},
139+
}
140+
}
141+
142+
// GetServiceCA returns the Service CA certificate as a string
143+
// Intended to be called in the reconcile loop. Returns an error if the CA
144+
// certificate isn't ready yet.
145+
func GetServiceCA(ctx context.Context, c client.Client, l logr.Logger, caNamespace string) (string, error) {
146+
log := l.WithValues("caNamespace", caNamespace)
147+
caCert := cmapi.Certificate{}
148+
if err := initializeServiceCA(ctx, log, c, caNamespace); err != nil {
149+
return "", err
150+
}
151+
err := c.Get(ctx, client.ObjectKey{
152+
Name: CACertName,
153+
Namespace: caNamespace,
154+
}, &caCert)
155+
if err != nil {
156+
log.Error(err, "fetching CA certificate")
157+
return "", err
158+
}
159+
160+
if !isCertReady(&caCert) {
161+
log.Info("CA certificate not yet ready")
162+
return "", fmt.Errorf("CA certificate not yet ready")
163+
}
164+
165+
secret := corev1.Secret{}
166+
if err := c.Get(ctx, client.ObjectKey{
167+
Name: caCert.Spec.SecretName,
168+
Namespace: caNamespace,
169+
}, &secret); err != nil {
170+
log.Error(err, "Fetching CA secret")
171+
return "", err
172+
}
173+
caBytes, ok := secret.Data["tls.crt"]
174+
if !ok {
175+
return "", fmt.Errorf("key `tls.crt` missing in CA secret")
176+
}
177+
178+
if !utf8.Valid(caBytes) {
179+
return "", fmt.Errorf("`tls.crt` in secret is not valid UTF-8")
180+
}
181+
182+
return string(caBytes), nil
183+
}
184+
185+
// initializeServiceCA checks that cert-manager CRDs exist and ensures that the service CA is setup
186+
func initializeServiceCA(ctx context.Context, l logr.Logger, c client.Client, caNamespace string) error {
187+
cmcrd := extv1.CustomResourceDefinition{}
188+
if err := c.Get(ctx, client.ObjectKey{Name: "certificates.cert-manager.io"}, &cmcrd); err != nil {
189+
return err
190+
}
191+
192+
// Ensure that service CA exists
193+
return ensureCA(ctx, c, l, caNamespace)
194+
}

0 commit comments

Comments
 (0)