Skip to content

Commit

Permalink
Allow config of http01 solver pod security context
Browse files Browse the repository at this point in the history
This allows configuration via extraArgs (--acme-http01-solver-security-context),
and takes a json string for the k8s podSecurityContext.

Signed-off-by: Adrian Lai <aidy@loathe.me.uk>
  • Loading branch information
aidy committed Aug 7, 2022
1 parent aeae4b3 commit fd26335
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 9 deletions.
11 changes: 10 additions & 1 deletion cmd/controller/app/controller.go
Expand Up @@ -18,6 +18,7 @@ package app

import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
Expand All @@ -26,6 +27,7 @@ import (
"time"

"golang.org/x/sync/errgroup"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/client-go/kubernetes"
Expand Down Expand Up @@ -261,6 +263,12 @@ func buildControllerContextFactory(ctx context.Context, opts *options.Controller
return nil, fmt.Errorf("error parsing ACMEHTTP01SolverResourceLimitsMemory: %w", err)
}

var http01SolverSecurityContext corev1.PodSecurityContext
err = json.Unmarshal([]byte(opts.ACMEHTTP01SolverSecurityContext), &http01SolverSecurityContext)
if err != nil {
return nil, fmt.Errorf("error parsing ACMEHTTP01SolverSecurityContext: %w", err)
}

acmeAccountRegistry := accounts.NewDefaultRegistry()

ctxFactory, err := controller.NewContextFactory(ctx, controller.ContextOptions{
Expand All @@ -281,7 +289,8 @@ func buildControllerContextFactory(ctx context.Context, opts *options.Controller
HTTP01SolverResourceLimitsMemory: http01SolverResourceLimitsMemory,
HTTP01SolverImage: opts.ACMEHTTP01SolverImage,
// Allows specifying a list of custom nameservers to perform HTTP01 checks on.
HTTP01SolverNameservers: opts.ACMEHTTP01SolverNameservers,
HTTP01SolverNameservers: opts.ACMEHTTP01SolverNameservers,
HTTP01SolverSecurityContext: http01SolverSecurityContext,

DNS01Nameservers: nameservers,
DNS01CheckRetryPeriod: opts.DNS01CheckRetryPeriod,
Expand Down
8 changes: 7 additions & 1 deletion cmd/controller/app/options/options.go
Expand Up @@ -81,7 +81,8 @@ type ControllerOptions struct {
ACMEHTTP01SolverResourceLimitsCPU string
ACMEHTTP01SolverResourceLimitsMemory string
// Allows specifying a list of custom nameservers to perform HTTP01 checks on.
ACMEHTTP01SolverNameservers []string
ACMEHTTP01SolverNameservers []string
ACMEHTTP01SolverSecurityContext string

ClusterIssuerAmbientCredentials bool
IssuerAmbientCredentials bool
Expand Down Expand Up @@ -155,6 +156,7 @@ var (
defaultACMEHTTP01SolverResourceRequestMemory = "64Mi"
defaultACMEHTTP01SolverResourceLimitsCPU = "100m"
defaultACMEHTTP01SolverResourceLimitsMemory = "64Mi"
defaultACMEHTTP01SolverSecurityContext = `{"runAsNonRoot": true, "seccompProfile": {"type": "RuntimeDefault"}}`

defaultAutoCertificateAnnotations = []string{"kubernetes.io/tls-acme"}

Expand Down Expand Up @@ -240,6 +242,7 @@ func NewControllerOptions() *ControllerOptions {
DefaultIssuerGroup: defaultTLSACMEIssuerGroup,
DefaultAutoCertificateAnnotations: defaultAutoCertificateAnnotations,
ACMEHTTP01SolverNameservers: []string{},
ACMEHTTP01SolverSecurityContext: defaultACMEHTTP01SolverSecurityContext,
DNS01RecursiveNameservers: []string{},
DNS01RecursiveNameserversOnly: defaultDNS01RecursiveNameserversOnly,
EnableCertificateOwnerRef: defaultEnableCertificateOwnerRef,
Expand Down Expand Up @@ -311,6 +314,9 @@ func (s *ControllerOptions) AddFlags(fs *pflag.FlagSet) {
"ACME HTTP01 check requests. This should be a list containing host and "+
"port, for example 8.8.8.8:53,8.8.4.4:53")

fs.StringVar(&s.ACMEHTTP01SolverSecurityContext, "acme-http01-solver-security-context", defaultACMEHTTP01SolverSecurityContext, ""+
"A JSON string defining the Security Context when spawning new ACME HTTP01 challenge solver pods.")

fs.BoolVar(&s.ClusterIssuerAmbientCredentials, "cluster-issuer-ambient-credentials", defaultClusterIssuerAmbientCredentials, ""+
"Whether a cluster-issuer may make use of ambient credentials for issuers. 'Ambient Credentials' are credentials drawn from the environment, metadata services, or local files which are not explicitly configured in the ClusterIssuer API object. "+
"When this flag is enabled, the following sources for credentials are also used: "+
Expand Down
4 changes: 4 additions & 0 deletions pkg/controller/context.go
Expand Up @@ -174,6 +174,10 @@ type ACMEOptions struct {
// for ACME HTTP01 validations.
HTTP01SolverNameservers []string

// HTTP01PodSecurityContext is the Security Context to use when creating ACME HTTP01
// solver pods
HTTP01SolverSecurityContext corev1.PodSecurityContext

// DNS01CheckAuthoritative is a flag for controlling if auth nss are used
// for checking propagation of an RR. This is the ideal scenario
DNS01CheckAuthoritative bool
Expand Down
8 changes: 8 additions & 0 deletions pkg/controller/test/context_builder.go
Expand Up @@ -24,6 +24,7 @@ import (
"testing"
"time"

corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -35,6 +36,7 @@ import (
"k8s.io/client-go/tools/cache"
"k8s.io/utils/clock"
fakeclock "k8s.io/utils/clock/testing"
"k8s.io/utils/pointer"
gwfake "sigs.k8s.io/gateway-api/pkg/client/clientset/gateway/versioned/fake"
gwinformers "sigs.k8s.io/gateway-api/pkg/client/informers/gateway/externalversions"

Expand Down Expand Up @@ -110,6 +112,12 @@ func (b *Builder) Init() {
if b.StringGenerator == nil {
b.StringGenerator = RandStringBytes
}
b.ACMEOptions.HTTP01SolverSecurityContext = corev1.PodSecurityContext{
RunAsNonRoot: pointer.BoolPtr(true),
SeccompProfile: &corev1.SeccompProfile{
Type: corev1.SeccompProfileTypeRuntimeDefault,
},
}
b.requiredReactors = make(map[string]bool)
b.Client = kubefake.NewSimpleClientset(b.KubeObjects...)
b.CMClient = cmfake.NewSimpleClientset(b.CertManagerObjects...)
Expand Down
9 changes: 2 additions & 7 deletions pkg/issuer/acme/http/pod.go
Expand Up @@ -172,13 +172,8 @@ func (s *Solver) buildDefaultPod(ch *cmacme.Challenge) *corev1.Pod {
NodeSelector: map[string]string{
"kubernetes.io/os": "linux",
},
RestartPolicy: corev1.RestartPolicyOnFailure,
SecurityContext: &corev1.PodSecurityContext{
RunAsNonRoot: pointer.BoolPtr(true),
SeccompProfile: &corev1.SeccompProfile{
Type: corev1.SeccompProfileTypeRuntimeDefault,
},
},
RestartPolicy: corev1.RestartPolicyOnFailure,
SecurityContext: &s.ACMEOptions.HTTP01SolverSecurityContext,
Containers: []corev1.Container{
{
Name: "acmesolver",
Expand Down
103 changes: 103 additions & 0 deletions pkg/issuer/acme/http/pod_test.go
Expand Up @@ -25,6 +25,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
coretesting "k8s.io/client-go/testing"
"k8s.io/utils/pointer"

cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1"
)
Expand Down Expand Up @@ -127,6 +128,108 @@ func TestEnsurePod(t *testing.T) {
}
},
},
"should have the correct default security context": {
Challenge: &cmacme.Challenge{
Spec: cmacme.ChallengeSpec{
DNSName: "example.com",
Token: "token",
Key: "key",
Solver: cmacme.ACMEChallengeSolver{
HTTP01: &cmacme.ACMEChallengeSolverHTTP01{
Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{},
},
},
},
},
PreFn: func(t *testing.T, s *solverFixture) {
expectedPod := s.Solver.buildPod(s.Challenge)
// create a reactor that fails the test if a pod is created
s.Builder.FakeKubeClient().PrependReactor("create", "pods", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
pod := action.(coretesting.CreateAction).GetObject().(*corev1.Pod)
// clear pod name as we don't know it yet in the expectedPod
pod.Name = ""
if !reflect.DeepEqual(pod, expectedPod) {
t.Errorf("Expected %v to equal %v", pod, expectedPod)
}
return false, ret, nil
})

s.Builder.Sync()
},
CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) {
resp := args[0].(*corev1.Pod)
err := args[1]
if resp == nil && err == nil {
t.Errorf("unexpected pod = nil")
t.Fail()
return
}
expected := &corev1.PodSecurityContext{
RunAsNonRoot: pointer.BoolPtr(true),
SeccompProfile: &corev1.SeccompProfile{
Type: corev1.SeccompProfileTypeRuntimeDefault,
},
}
if !reflect.DeepEqual(resp.Spec.SecurityContext, expected) {
t.Errorf("Expected %v to equal %v", resp.Spec.SecurityContext, expected)
}

},
},
"security context should be configurable": {
Challenge: &cmacme.Challenge{
Spec: cmacme.ChallengeSpec{
DNSName: "example.com",
Token: "token",
Key: "key",
Solver: cmacme.ACMEChallengeSolver{
HTTP01: &cmacme.ACMEChallengeSolverHTTP01{
Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{},
},
},
},
},
PreFn: func(t *testing.T, s *solverFixture) {
s.Solver.ACMEOptions.HTTP01SolverSecurityContext = corev1.PodSecurityContext{
RunAsUser: pointer.Int64(1020),
SeccompProfile: &corev1.SeccompProfile{
Type: corev1.SeccompProfileTypeRuntimeDefault,
},
}
expectedPod := s.Solver.buildPod(s.Challenge)
// create a reactor that fails the test if a pod is created
s.Builder.FakeKubeClient().PrependReactor("create", "pods", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
pod := action.(coretesting.CreateAction).GetObject().(*corev1.Pod)
// clear pod name as we don't know it yet in the expectedPod
pod.Name = ""
if !reflect.DeepEqual(pod, expectedPod) {
t.Errorf("Expected %v to equal %v", pod, expectedPod)
}
return false, ret, nil
})

s.Builder.Sync()
},
CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) {
resp := args[0].(*corev1.Pod)
err := args[1]
if resp == nil && err == nil {
t.Errorf("unexpected pod = nil")
t.Fail()
return
}
expected := &corev1.PodSecurityContext{
RunAsUser: pointer.Int64(1020),
SeccompProfile: &corev1.SeccompProfile{
Type: corev1.SeccompProfileTypeRuntimeDefault,
},
}
if !reflect.DeepEqual(resp.Spec.SecurityContext, expected) {
t.Errorf("Expected %v to equal %v", resp.Spec.SecurityContext, expected)
}

},
},
"should clean up if multiple pods exist": {
Challenge: &cmacme.Challenge{
Spec: cmacme.ChallengeSpec{
Expand Down

0 comments on commit fd26335

Please sign in to comment.