Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support client certificate authentication #113

Merged
merged 2 commits into from
Mar 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ func main() {

srv.TLSConfig.CipherSuites = cipherSuiteIDs
srv.TLSConfig.MinVersion = version
srv.TLSConfig.ClientAuth = tls.RequestClientCert

if err := http2.ConfigureServer(srv, nil); err != nil {
klog.Fatalf("failed to configure http2 server: %v", err)
Expand Down Expand Up @@ -388,14 +389,14 @@ func initKubeConfig(kcLocation string) *rest.Config {
if kcLocation != "" {
kubeConfig, err := clientcmd.BuildConfigFromFlags("", kcLocation)
if err != nil {
klog.Fatalf("unable to build rest config based on provided path to kubeconfig file: %v",err)
klog.Fatalf("unable to build rest config based on provided path to kubeconfig file: %v", err)
}
return kubeConfig
}

kubeConfig, err := rest.InClusterConfig()
if err != nil {
klog.Fatalf("cannot find Service Account in pod to build in-cluster rest config: %v",err)
klog.Fatalf("cannot find Service Account in pod to build in-cluster rest config: %v", err)
}

return kubeConfig
Expand Down
124 changes: 124 additions & 0 deletions test/e2e/basics.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,130 @@ func testTokenAudience(s *kubetest.Suite) kubetest.TestSuite {
}
}

func testClientCertificates(s *kubetest.Suite) kubetest.TestSuite {
return func(t *testing.T) {
command := `curl --connect-timeout 5 -v -s -k --fail --cert /certs/tls.crt --key /certs/tls.key https://kube-rbac-proxy.default.svc.cluster.local:8443/metrics`

kubetest.Scenario{
Name: "NoRBAC",
Description: `
As a client with client certificates authorization without RBAC,
I fail with my request
`,

Given: kubetest.Setups(
kubetest.CreatedManifests(
s.KubeClient,
"clientcertificates/certificate.yaml",
"clientcertificates/clusterRole.yaml",
"clientcertificates/clusterRoleBinding.yaml",
"clientcertificates/deployment.yaml",
"clientcertificates/service.yaml",
"clientcertificates/serviceAccount.yaml",
),
),
When: kubetest.Conditions(
kubetest.PodsAreReady(
s.KubeClient,
1,
"app=kube-rbac-proxy",
),
kubetest.ServiceIsReady(
s.KubeClient,
"kube-rbac-proxy",
),
),
Then: kubetest.Checks(
ClientFails(
s.KubeClient,
command,
&kubetest.RunOptions{ClientCertificates: true},
),
),
}.Run(t)

kubetest.Scenario{
Name: "WithRBAC",
Description: `
As a client with client certificates authorization with RBAC,
I succeed with my request
`,

Given: kubetest.Setups(
kubetest.CreatedManifests(
s.KubeClient,
"clientcertificates/certificate.yaml",
"clientcertificates/clusterRole.yaml",
"clientcertificates/clusterRoleBinding.yaml",
"clientcertificates/deployment.yaml",
"clientcertificates/service.yaml",
"clientcertificates/serviceAccount.yaml",
"clientcertificates/clusterRole-client.yaml",
"clientcertificates/clusterRoleBinding-client.yaml",
),
),
When: kubetest.Conditions(
kubetest.PodsAreReady(
s.KubeClient,
1,
"app=kube-rbac-proxy",
),
kubetest.ServiceIsReady(
s.KubeClient,
"kube-rbac-proxy",
),
),
Then: kubetest.Checks(
ClientSucceeds(
s.KubeClient,
command,
&kubetest.RunOptions{ClientCertificates: true},
),
),
}.Run(t)

kubetest.Scenario{
Name: "WrongCA",
Description: `
As a client with client certificates authorization with RBAC and with unmatched CA,
I fail with my request
`,

Given: kubetest.Setups(
kubetest.CreatedManifests(
s.KubeClient,
"clientcertificates/certificate.yaml",
"clientcertificates/clusterRole.yaml",
"clientcertificates/clusterRoleBinding.yaml",
"clientcertificates/deployment-wrongca.yaml",
"clientcertificates/service.yaml",
"clientcertificates/serviceAccount.yaml",
"clientcertificates/clusterRole-client.yaml",
"clientcertificates/clusterRoleBinding-client.yaml",
),
),
When: kubetest.Conditions(
kubetest.PodsAreReady(
s.KubeClient,
1,
"app=kube-rbac-proxy",
),
kubetest.ServiceIsReady(
s.KubeClient,
"kube-rbac-proxy",
),
),
Then: kubetest.Checks(
ClientFails(
s.KubeClient,
command,
&kubetest.RunOptions{ClientCertificates: true},
),
),
}.Run(t)
}
}

func testAllowPathsRegexp(s *kubetest.Suite) kubetest.TestSuite {
return func(t *testing.T) {
command := `STATUS_CODE=$(curl --connect-timeout 5 -o /dev/null -v -s -k --write-out "%%{http_code}" -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" https://kube-rbac-proxy.default.svc.cluster.local:8443%s); if [[ "$STATUS_CODE" != %d ]]; then echo "expecting %d status code, got $STATUS_CODE instead" > /proc/self/fd/2; exit 1; fi`
Expand Down
9 changes: 9 additions & 0 deletions test/e2e/clientcertificates/certificate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: v1
kind: Secret
metadata:
name: kube-rbac-proxy-client-certificates
type: kubernetes.io/tls
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURhakNDQWxLZ0F3SUJBZ0lVYzlWWlg2dDNqVHRwZWZycjQzcGQzVW8vNGFBd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0xERXFNQ2dHQTFVRUF4TWhhM1ZpWlMxeVltRmpMWEJ5YjNoNUxXTmxjblJwWm1sallYUmxjeTEwWlhOMApNQjRYRFRJeE1ETXhOakUzTWpVd01Gb1hEVE14TURNeE5ERTNNalV3TUZvd0xERXFNQ2dHQTFVRUF4TWhhM1ZpClpTMXlZbUZqTFhCeWIzaDVMV05sY25ScFptbGpZWFJsY3kxMFpYTjBNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUYKQUFPQ0FROEFNSUlCQ2dLQ0FRRUEvVjdzelZNdXl5ZFk1bHMwdkxyVWh0cE1kWmtVV2ZWQTIyQW5tbGhtNk5oeQowVjlMK1dKc3VZbTlicjl4VUJLV1JPaGVkNk5SaGYxRkczL1JBRWROM1l0MUM0eEw4TUxOTzUwbVYvTGdTL3lFCnlXaUlPdEhaRk9SM1NzcWVCVlJWd2N2ekhmdnFKQTl2Ujh4MVR4NVAyd1ZlRHpOWHVQSGJCSW1KREcwZXNSWjkKWTBZeGZCK3M2eTJaeUxRK3JXSDRheDRjVHlaWGVSSGZoZmhMdStkOTJScDIvSmx2aDRaUXJzcFIyQ0YxZE5CQgpxNnE3LzdreWd3TmVsTmZkeU9sbDlCT1hOVHBKQmdYa3FCQUE4RWllWkhuUGZ5TGJuYVdVSzFQS2REV2hwOTNWCnRiSU9IZklXamc0OTdSZ2RsazhGTlptTkxJbjI1dzB0VHVSWktOQzM2UUlEQVFBQm80R0RNSUdBTUE0R0ExVWQKRHdFQi93UUVBd0lGb0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREFqQU1CZ05WSFJNQkFmOEVBakFBTUIwRwpBMVVkRGdRV0JCUjdyTjFscFB2Qlk3YjdqUjMxbDF5aStaSDVRREFzQmdOVkhSRUVKVEFqZ2lGcmRXSmxMWEppCllXTXRjSEp2ZUhrdFkyVnlkR2xtYVdOaGRHVnpMWFJsYzNRd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFFZnQKUFkxc3RQSk1IeEE4UjVOQk01TTVwV0lDK3hENVZHc3RhV0M3a0RTWit5TjdMS1lSQjdVV0d5c2dCbkZsK2hqOApHWVBOQUgrbkNDOWJEODhSQi9aWkhBZ3dtREJPbG01OVUvSjUzL0pLQlhFS0l3cjg2WmI0SU9pYlVPbG94RDJBCnUwVTJteGRQb3ZBSjZ1akplN3k5YTh0QUFTV1BVck9xUElVWHNkbFR6RmJ2UktxN3hScTZXaWtKQndqRzhYd0oKd0NNc2Jja2pES2Q1T2tMWTk4WkJWWmlFcGoyaXFJdDNCSk95cnV0eEh1N05Pcm5UdEdCVmlSZlVrRlltcEVoTApDYkp3bnNCbitCTWFSd3FxNWRtZXJzV0RnREtYK1ZGYXZzUVZvN1VubGRFcmlvVzZlaTFxZnV1M1NsZDF5a3NPCkx2QW9ISDRxZXIwS2loTGMwZms9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBL1Y3c3pWTXV5eWRZNWxzMHZMclVodHBNZFprVVdmVkEyMkFubWxobTZOaHkwVjlMCitXSnN1WW05YnI5eFVCS1dST2hlZDZOUmhmMUZHMy9SQUVkTjNZdDFDNHhMOE1MTk81MG1WL0xnUy95RXlXaUkKT3RIWkZPUjNTc3FlQlZSVndjdnpIZnZxSkE5dlI4eDFUeDVQMndWZUR6Tlh1UEhiQkltSkRHMGVzUlo5WTBZeApmQitzNnkyWnlMUStyV0g0YXg0Y1R5WlhlUkhmaGZoTHUrZDkyUnAyL0psdmg0WlFyc3BSMkNGMWROQkJxNnE3Ci83a3lnd05lbE5mZHlPbGw5Qk9YTlRwSkJnWGtxQkFBOEVpZVpIblBmeUxibmFXVUsxUEtkRFdocDkzVnRiSU8KSGZJV2pnNDk3UmdkbGs4Rk5abU5MSW4yNXcwdFR1UlpLTkMzNlFJREFRQUJBb0lCQUNnMUNCOE5ORC9JM3JLdgpobzdzbHcxUFZ4TFNXQWh1Z3Z4TkpmdTRTNXhudk5DODdyR0VqUHhrZjBzejFpZCt5NW5qeGhuMk1ObXlkMlVGCnc0VG55OU44YmZhSExRWG40K214NW9QT1p3bW42T3FOVEJFSmZBbDB0L21HYmMwcXRQRXNERWlWMFhJbmdPRkQKOE5tOVZhN01DMEVlUksxMHMremtabnN5VmN5RUZFemRibzFHQnQ3Sjg2c3JkR3JGekV1Yk1wSHRvcHdLYkpCUApQVkE1eEo4dXJ5T0t0Rm9PZUM5M0NMaWozL1k3dE9iWTkzNGVVRFlmek5mbnNFajlSWjJlVkt5bnZSMjMvMDh4Cm1kZVdnVDIzMEFGai95U0JZT0c1bXRObHBVaVlySmZhRHpzbmViOUFuTElXVmU1WitlcnF2ZTB6MGkrb2lCK0EKWFIrVjVJRUNnWUVBL2xDQ3orbXc5Z25Rc1ZlZFRZMm5vSVkxSWNKcmttYnIwZ3dWNS9YVmg3QUpyZjJkUVpmcQpyUDIvWVNwNUY0Qkh3WXBzU0ZuZER4ZEMyQ24yUThzSWhZWm5MUC81eFd2UmlaVFpQcGdFamRYdksvUXBOdmorCk41MnZHUHBJLzN1TTRoTldtQytEUnhTOCtjOTUwbU5Jakt3R1AyTGQwc09BelJKb3RTS3lJMThDZ1lFQS93elEKR05jK0sxY2FTWmgrN1cwUWEvSWR6SXBjaDJTSkZ3N2NpbUpBdGI1QWUxcUNTV05VVEU3K2g0ZUoxRUVMeFdKSAo4ZVhwOXM2YmJqUUdhK1VnVWZ0Y2FGeTVsQUp5cUJlR0Yvb2d5NGVma1pmZm90dkhoamwrK0F4c2EzakZ5OWZCCnI3TE1DQVdxWUFNbjhVbDZEdDIrYU1TQ2FKVWJKb2wzeVF2ejhiY0NnWUFaMkF5ekhFaURlRlBnOGNwbWl6S3gKdVhIRTBJRW1DNWVEYlA3ODU1cWZnMkE0Y2tGODNQZFlSU0VodXJNN2xDbTJuVjZMcTdZdlJtbmdsY01VK0prUgpHS3Q3Z3pmYXZDNDdFUWxTdHhnZllkSkFZVHVlL29hM0dDc25HcWc0YmxITzE3QkJIdkwwVWtNeUQ4ZU5mZEZ0Cm1qMjRTMC9IajE2VGVIOXppT2NaZVFLQmdRQ1pMVmpOa25nRUQ0djZKMXdUdkN6Z3A4aTl6MXRDOVY1Z09zeTUKVDhrTlhmWGNINytmMDhnTkRHUlJnVldGNUlydFFObDBybUNWbWdBL2IzOXJ6WEJiekZyelVyMGg1MVoxSGF1cgpPczMzYnJ5bTlFK2J2K05VK2JNSHhtNVhIWEd0dXliWUhzbnJCM3dMcmRtUFVGRytHKzcvZlFYVlNiZkVyVXNECnMrb09nUUtCZ1FESDJrR2NsZkdqZWpPUThpZTFIeTViQUg4M2RpdzVzRWJHdUlabDRjRGc3VlpUY08zR3dPWG4KbGFyL1RVTVR0S0ROWHVWSU02ZWtKTVlZZ2V4V1VGaFRCTlN4VGc0cjUxR2w4a09UZ0tsMUFWemgzZHRYVzRhaAorWHp6QkJMOUsxZXpEajQzT2JaY3FVeEtraStSTzN5Y2RuaFFtMzZGVWVyVUlXRGNZVVV3SGc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLRENDQWhDZ0F3SUJBZ0lVZkUvWU1TT2NzZkVIOGN2TWxvRFkvS1prUnA4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd0xERXFNQ2dHQTFVRUF4TWhhM1ZpWlMxeVltRmpMWEJ5YjNoNUxXTmxjblJwWm1sallYUmxjeTEwWlhOMApNQjRYRFRJeE1ETXhOakUzTWpVd01Gb1hEVEkyTURNeE5URTNNalV3TUZvd0xERXFNQ2dHQTFVRUF4TWhhM1ZpClpTMXlZbUZqTFhCeWIzaDVMV05sY25ScFptbGpZWFJsY3kxMFpYTjBNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUYKQUFPQ0FROEFNSUlCQ2dLQ0FRRUFyeFJ4bGZOOFl5aG0rY0RheW0wNENLSmkzSlJ6d0FVQ1hPZkZidzd6VDBuZwo3SnNNQ0hONDVDZmFLSElTV0diL1AwWkRrSDBCUVpGcEczcmx2T2NDS2szbDFieHFCMGcwL0p4ekhvSWVTVW1wCndrRG1HSm5rcHQ5dVVaZFpKbGFCZkZNZ2RjUlNkaFZZdlZMYXRGdUNsTENlbmVvek9RcDkya3dPRStpUTBreWUKNTd6QXpWN0d0ZWd0UHVnQ2V3bmpvOFg5amg4bXlaRlZBbGFnRmVpUjJjNWZRbGJZLzMwS1VieTNkeTNodTRNWApmMlN6a3l0dFpSUkxhaHo4eWdieXptMExMOHBuZ1RPcFNNRGkrcDNnRFg3QmRUUnQ2QUgvUkp0aVVPdTJjS0hzCkdUZG51SEdScldLZFZJQ20zWUFhRVBXN3hwSG92Y3dzaGdrOTFpbXByUUlEQVFBQm8wSXdRREFPQmdOVkhROEIKQWY4RUJBTUNBUVl3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVQkRIcGEvNXlFSUh3VVF4aQoxS1loakJZQnQ0a3dEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBQlZKQzIya2lrQXhlV0MycGVPQ2ZHQW96ZUtBCksvUTBpSDRWeXYzRnpCaExZMDRHcmEwYWpNbGZXU3hERVRXU2xYVHp5cnIwR1NMNEdJMWZlUHN5MTcwcVpBTnYKaTRCZGRWVHEvY0p6NittVythekNaeHFid2o3RDkvNDFUa3BhOW5xZ3ZBSVJwN3laQXcrSnY1T2ZuUXQ5VFkrcgphNDlqMEdVZ1VKUGlxZGc1M25NWUhmQWw0QThTN0U5VytrZWJpMXRBL1RYcVVhMGtGNjNMbUpWSGcybGVDUllRCjRmK1ZDd3g0TVZDOHhRdE9NZVp2T1YzQVNLRmxvV1Rab1drUFlwcURSaEdVaGdxMjhLMDdUZ2RYck0rYUhFdWUKK3hFN24rd3JJSUhxZTJZbkVwT010ajJpZThnUDJ6cmdTRVk5ZytVbjV0SzJlUFVBSGtmVm9mVE9ZZDA9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
7 changes: 7 additions & 0 deletions test/e2e/clientcertificates/clusterRole-client.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: metrics
rules:
- nonResourceURLs: ["/metrics"]
verbs: ["get"]
13 changes: 13 additions & 0 deletions test/e2e/clientcertificates/clusterRole.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kube-rbac-proxy
rules:
- apiGroups: ["authentication.k8s.io"]
resources:
- tokenreviews
verbs: ["create"]
- apiGroups: ["authorization.k8s.io"]
resources:
- subjectaccessreviews
verbs: ["create"]
11 changes: 11 additions & 0 deletions test/e2e/clientcertificates/clusterRoleBinding-client.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: metrics
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: metrics
subjects:
- kind: User
name: kube-rbac-proxy-certificates-test
12 changes: 12 additions & 0 deletions test/e2e/clientcertificates/clusterRoleBinding.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kube-rbac-proxy
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kube-rbac-proxy
subjects:
- kind: ServiceAccount
name: kube-rbac-proxy
namespace: default
39 changes: 39 additions & 0 deletions test/e2e/clientcertificates/deployment-wrongca.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: kube-rbac-proxy
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: kube-rbac-proxy
template:
metadata:
labels:
app: kube-rbac-proxy
spec:
serviceAccountName: kube-rbac-proxy
containers:
- name: kube-rbac-proxy
image: quay.io/brancz/kube-rbac-proxy:local
args:
- "--secure-listen-address=0.0.0.0:8443"
- "--upstream=http://127.0.0.1:8081/"
- "--logtostderr=true"
- "--client-ca-file=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
- "--v=10"
ports:
- containerPort: 8443
name: https
volumeMounts:
- mountPath: /certs
name: certs
- name: prometheus-example-app
image: quay.io/brancz/prometheus-example-app:v0.1.0
args:
- "--bind=127.0.0.1:8081"
volumes:
- name: certs
secret:
secretName: kube-rbac-proxy-client-certificates
39 changes: 39 additions & 0 deletions test/e2e/clientcertificates/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: kube-rbac-proxy
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: kube-rbac-proxy
template:
metadata:
labels:
app: kube-rbac-proxy
spec:
serviceAccountName: kube-rbac-proxy
containers:
- name: kube-rbac-proxy
image: quay.io/brancz/kube-rbac-proxy:local
args:
- "--secure-listen-address=0.0.0.0:8443"
- "--upstream=http://127.0.0.1:8081/"
- "--logtostderr=true"
- "--client-ca-file=/certs/ca.crt"
- "--v=10"
ports:
- containerPort: 8443
name: https
volumeMounts:
- mountPath: /certs
name: certs
- name: prometheus-example-app
image: quay.io/brancz/prometheus-example-app:v0.1.0
args:
- "--bind=127.0.0.1:8081"
volumes:
- name: certs
secret:
secretName: kube-rbac-proxy-client-certificates
14 changes: 14 additions & 0 deletions test/e2e/clientcertificates/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: kube-rbac-proxy
name: kube-rbac-proxy
namespace: default
spec:
ports:
- name: https
port: 8443
targetPort: https
selector:
app: kube-rbac-proxy
5 changes: 5 additions & 0 deletions test/e2e/clientcertificates/serviceAccount.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: kube-rbac-proxy
namespace: default
9 changes: 5 additions & 4 deletions test/e2e/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ func TestMain(m *testing.M) {

func Test(t *testing.T) {
tests := map[string]kubetest.TestSuite{
"Basics": testBasics(suite),
"TokenAudience": testTokenAudience(suite),
"AllowPath": testAllowPathsRegexp(suite),
"IgnorePath": testIgnorePaths(suite),
"Basics": testBasics(suite),
"ClientCertificates": testClientCertificates(suite),
"TokenAudience": testTokenAudience(suite),
"AllowPath": testAllowPathsRegexp(suite),
"IgnorePath": testIgnorePaths(suite),
}

for name, tc := range tests {
Expand Down
Loading