Replace godep with dep

This commit is contained in:
Manuel de Brito Fontes 2017-10-06 17:26:14 -03:00
parent 1e7489927c
commit bf5616c65b
14883 changed files with 3937406 additions and 361781 deletions

View file

@ -0,0 +1,66 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"certificate_controller.go",
"certificate_controller_utils.go",
],
visibility = [
":__subpackages__",
"//cmd/gke-certificates-controller:__subpackages__",
],
deps = [
"//pkg/controller:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/informers/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//vendor/k8s.io/client-go/listers/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/k8s.io/client-go/tools/record:go_default_library",
"//vendor/k8s.io/client-go/util/workqueue:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/controller/certificates/approver:all-srcs",
"//pkg/controller/certificates/signer:all-srcs",
],
tags = ["automanaged"],
visibility = [
"//pkg/controller:__pkg__",
],
)
go_test(
name = "go_default_test",
srcs = ["certificate_controller_test.go"],
library = ":go_default_library",
deps = [
"//pkg/controller:go_default_library",
"//vendor/k8s.io/api/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
],
)

View file

@ -0,0 +1,3 @@
reviewers:
- deads2k
- mikedanese

View file

@ -0,0 +1,50 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["sarapprove_test.go"],
library = ":go_default_library",
deps = [
"//pkg/apis/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/api/authorization/v1beta1:go_default_library",
"//vendor/k8s.io/api/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
"//vendor/k8s.io/client-go/testing:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["sarapprove.go"],
deps = [
"//pkg/apis/certificates/v1beta1:go_default_library",
"//pkg/controller/certificates:go_default_library",
"//pkg/features:go_default_library",
"//vendor/k8s.io/api/authorization/v1beta1:go_default_library",
"//vendor/k8s.io/api/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/client-go/informers/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,228 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package approver implements an automated approver for kubelet certificates.
package approver
import (
"crypto/x509"
"fmt"
"reflect"
"strings"
authorization "k8s.io/api/authorization/v1beta1"
capi "k8s.io/api/certificates/v1beta1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
certificatesinformers "k8s.io/client-go/informers/certificates/v1beta1"
clientset "k8s.io/client-go/kubernetes"
k8s_certificates_v1beta1 "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
"k8s.io/kubernetes/pkg/controller/certificates"
"k8s.io/kubernetes/pkg/features"
)
type csrRecognizer struct {
recognize func(csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool
permission authorization.ResourceAttributes
successMessage string
}
type sarApprover struct {
client clientset.Interface
recognizers []csrRecognizer
}
func NewCSRApprovingController(client clientset.Interface, csrInformer certificatesinformers.CertificateSigningRequestInformer) (*certificates.CertificateController, error) {
approver := &sarApprover{
client: client,
recognizers: recognizers(),
}
return certificates.NewCertificateController(
client,
csrInformer,
approver.handle,
)
}
func recognizers() []csrRecognizer {
recognizers := []csrRecognizer{
{
recognize: isSelfNodeClientCert,
permission: authorization.ResourceAttributes{Group: "certificates.k8s.io", Resource: "certificatesigningrequests", Verb: "create", Subresource: "selfnodeclient"},
successMessage: "Auto approving self kubelet client certificate after SubjectAccessReview.",
},
{
recognize: isNodeClientCert,
permission: authorization.ResourceAttributes{Group: "certificates.k8s.io", Resource: "certificatesigningrequests", Verb: "create", Subresource: "nodeclient"},
successMessage: "Auto approving kubelet client certificate after SubjectAccessReview.",
},
}
if utilfeature.DefaultFeatureGate.Enabled(features.RotateKubeletServerCertificate) {
recognizers = append(recognizers, csrRecognizer{
recognize: isSelfNodeServerCert,
permission: authorization.ResourceAttributes{Group: "certificates.k8s.io", Resource: "certificatesigningrequests", Verb: "create", Subresource: "selfnodeserver"},
successMessage: "Auto approving self kubelet server certificate after SubjectAccessReview.",
})
}
return recognizers
}
func (a *sarApprover) handle(csr *capi.CertificateSigningRequest) error {
if len(csr.Status.Certificate) != 0 {
return nil
}
if approved, denied := certificates.GetCertApprovalCondition(&csr.Status); approved || denied {
return nil
}
x509cr, err := k8s_certificates_v1beta1.ParseCSR(csr)
if err != nil {
return fmt.Errorf("unable to parse csr %q: %v", csr.Name, err)
}
tried := []string{}
for _, r := range a.recognizers {
if !r.recognize(csr, x509cr) {
continue
}
tried = append(tried, r.permission.Subresource)
approved, err := a.authorize(csr, r.permission)
if err != nil {
return err
}
if approved {
appendApprovalCondition(csr, r.successMessage)
_, err = a.client.Certificates().CertificateSigningRequests().UpdateApproval(csr)
if err != nil {
return fmt.Errorf("error updating approval for csr: %v", err)
}
return nil
}
}
if len(tried) != 0 {
return fmt.Errorf("recognized csr %q as %v but subject access review was not approved", csr.Name, tried)
}
return nil
}
func (a *sarApprover) authorize(csr *capi.CertificateSigningRequest, rattrs authorization.ResourceAttributes) (bool, error) {
extra := make(map[string]authorization.ExtraValue)
for k, v := range csr.Spec.Extra {
extra[k] = authorization.ExtraValue(v)
}
sar := &authorization.SubjectAccessReview{
Spec: authorization.SubjectAccessReviewSpec{
User: csr.Spec.Username,
UID: csr.Spec.UID,
Groups: csr.Spec.Groups,
Extra: extra,
ResourceAttributes: &rattrs,
},
}
sar, err := a.client.AuthorizationV1beta1().SubjectAccessReviews().Create(sar)
if err != nil {
return false, err
}
return sar.Status.Allowed, nil
}
func appendApprovalCondition(csr *capi.CertificateSigningRequest, message string) {
csr.Status.Conditions = append(csr.Status.Conditions, capi.CertificateSigningRequestCondition{
Type: capi.CertificateApproved,
Reason: "AutoApproved",
Message: message,
})
}
func hasExactUsages(csr *capi.CertificateSigningRequest, usages []capi.KeyUsage) bool {
if len(usages) != len(csr.Spec.Usages) {
return false
}
usageMap := map[capi.KeyUsage]struct{}{}
for _, u := range usages {
usageMap[u] = struct{}{}
}
for _, u := range csr.Spec.Usages {
if _, ok := usageMap[u]; !ok {
return false
}
}
return true
}
var kubeletClientUsages = []capi.KeyUsage{
capi.UsageKeyEncipherment,
capi.UsageDigitalSignature,
capi.UsageClientAuth,
}
func isNodeClientCert(csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool {
if !reflect.DeepEqual([]string{"system:nodes"}, x509cr.Subject.Organization) {
return false
}
if (len(x509cr.DNSNames) > 0) || (len(x509cr.EmailAddresses) > 0) || (len(x509cr.IPAddresses) > 0) {
return false
}
if !hasExactUsages(csr, kubeletClientUsages) {
return false
}
if !strings.HasPrefix(x509cr.Subject.CommonName, "system:node:") {
return false
}
return true
}
func isSelfNodeClientCert(csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool {
if !isNodeClientCert(csr, x509cr) {
return false
}
if csr.Spec.Username != x509cr.Subject.CommonName {
return false
}
return true
}
var kubeletServerUsages = []capi.KeyUsage{
capi.UsageKeyEncipherment,
capi.UsageDigitalSignature,
capi.UsageServerAuth,
}
func isSelfNodeServerCert(csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool {
if !reflect.DeepEqual([]string{"system:nodes"}, x509cr.Subject.Organization) {
return false
}
if len(x509cr.DNSNames) == 0 || len(x509cr.IPAddresses) == 0 {
return false
}
if !hasExactUsages(csr, kubeletServerUsages) {
return false
}
if !strings.HasPrefix(x509cr.Subject.CommonName, "system:node:") {
return false
}
if csr.Spec.Username != x509cr.Subject.CommonName {
return false
}
return true
}

View file

@ -0,0 +1,370 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package approver
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/rand"
"net"
"testing"
authorization "k8s.io/api/authorization/v1beta1"
capi "k8s.io/api/certificates/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/fake"
testclient "k8s.io/client-go/testing"
k8s_certificates_v1beta1 "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
)
func TestHasKubeletUsages(t *testing.T) {
cases := []struct {
usages []capi.KeyUsage
expected bool
}{
{
usages: nil,
expected: false,
},
{
usages: []capi.KeyUsage{},
expected: false,
},
{
usages: []capi.KeyUsage{
capi.UsageKeyEncipherment,
capi.UsageDigitalSignature,
},
expected: false,
},
{
usages: []capi.KeyUsage{
capi.UsageKeyEncipherment,
capi.UsageDigitalSignature,
capi.UsageServerAuth,
},
expected: false,
},
{
usages: []capi.KeyUsage{
capi.UsageKeyEncipherment,
capi.UsageDigitalSignature,
capi.UsageClientAuth,
},
expected: true,
},
}
for _, c := range cases {
if hasExactUsages(&capi.CertificateSigningRequest{
Spec: capi.CertificateSigningRequestSpec{
Usages: c.usages,
},
}, kubeletClientUsages) != c.expected {
t.Errorf("unexpected result of hasKubeletUsages(%v), expecting: %v", c.usages, c.expected)
}
}
}
func TestHandle(t *testing.T) {
cases := []struct {
message string
allowed bool
recognized bool
err bool
verify func(*testing.T, []testclient.Action)
}{
{
recognized: false,
allowed: false,
verify: func(t *testing.T, as []testclient.Action) {
if len(as) != 0 {
t.Errorf("expected no client calls but got: %#v", as)
}
},
},
{
recognized: false,
allowed: true,
verify: func(t *testing.T, as []testclient.Action) {
if len(as) != 0 {
t.Errorf("expected no client calls but got: %#v", as)
}
},
},
{
recognized: true,
allowed: false,
verify: func(t *testing.T, as []testclient.Action) {
if len(as) != 1 {
t.Errorf("expected 1 call but got: %#v", as)
return
}
_ = as[0].(testclient.CreateActionImpl)
},
err: true,
},
{
recognized: true,
allowed: true,
verify: func(t *testing.T, as []testclient.Action) {
if len(as) != 2 {
t.Errorf("expected two calls but got: %#v", as)
return
}
_ = as[0].(testclient.CreateActionImpl)
a := as[1].(testclient.UpdateActionImpl)
if got, expected := a.Verb, "update"; got != expected {
t.Errorf("got: %v, expected: %v", got, expected)
}
if got, expected := a.Resource, (schema.GroupVersionResource{Group: "certificates.k8s.io", Version: "v1beta1", Resource: "certificatesigningrequests"}); got != expected {
t.Errorf("got: %v, expected: %v", got, expected)
}
if got, expected := a.Subresource, "approval"; got != expected {
t.Errorf("got: %v, expected: %v", got, expected)
}
csr := a.Object.(*capi.CertificateSigningRequest)
if len(csr.Status.Conditions) != 1 {
t.Errorf("expected CSR to have approved condition: %#v", csr)
}
c := csr.Status.Conditions[0]
if got, expected := c.Type, capi.CertificateApproved; got != expected {
t.Errorf("got: %v, expected: %v", got, expected)
}
if got, expected := c.Reason, "AutoApproved"; got != expected {
t.Errorf("got: %v, expected: %v", got, expected)
}
},
},
}
for _, c := range cases {
t.Run(fmt.Sprintf("recognized:%v,allowed: %v,err: %v", c.recognized, c.allowed, c.err), func(t *testing.T) {
client := &fake.Clientset{}
client.AddReactor("create", "subjectaccessreviews", func(action testclient.Action) (handled bool, ret runtime.Object, err error) {
return true, &authorization.SubjectAccessReview{
Status: authorization.SubjectAccessReviewStatus{
Allowed: c.allowed,
},
}, nil
})
approver := sarApprover{
client: client,
recognizers: []csrRecognizer{
{
successMessage: "tester",
permission: authorization.ResourceAttributes{Group: "foo", Resource: "bar", Subresource: "baz"},
recognize: func(csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool {
return c.recognized
},
},
},
}
csr := makeTestCsr()
if err := approver.handle(csr); err != nil && !c.err {
t.Errorf("unexpected err: %v", err)
}
c.verify(t, client.Actions())
})
}
}
func TestSelfNodeServerCertRecognizer(t *testing.T) {
defaultCSR := csrBuilder{
cn: "system:node:foo",
orgs: []string{"system:nodes"},
requestor: "system:node:foo",
usages: []capi.KeyUsage{
capi.UsageKeyEncipherment,
capi.UsageDigitalSignature,
capi.UsageServerAuth,
},
dns: []string{"node"},
ips: []net.IP{net.ParseIP("192.168.0.1")},
}
testCases := []struct {
description string
csrBuilder csrBuilder
expectedOutcome bool
}{
{
description: "Success - all requirements met",
csrBuilder: defaultCSR,
expectedOutcome: true,
},
{
description: "No organization",
csrBuilder: func(b csrBuilder) csrBuilder {
b.orgs = []string{}
return b
}(defaultCSR),
expectedOutcome: false,
},
{
description: "Wrong organization",
csrBuilder: func(b csrBuilder) csrBuilder {
b.orgs = append(b.orgs, "new-org")
return b
}(defaultCSR),
expectedOutcome: false,
},
{
description: "Wrong usages",
csrBuilder: func(b csrBuilder) csrBuilder {
b.usages = []capi.KeyUsage{}
return b
}(defaultCSR),
expectedOutcome: false,
},
{
description: "Wrong common name",
csrBuilder: func(b csrBuilder) csrBuilder {
b.cn = "wrong-common-name"
return b
}(defaultCSR),
expectedOutcome: false,
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
csr := makeFancyTestCsr(tc.csrBuilder)
x509cr, err := k8s_certificates_v1beta1.ParseCSR(csr)
if err != nil {
t.Errorf("unexpected err: %v", err)
}
if isSelfNodeServerCert(csr, x509cr) != tc.expectedOutcome {
t.Errorf("expected recognized to be %v", tc.expectedOutcome)
}
})
}
}
func TestRecognizers(t *testing.T) {
goodCases := []func(b *csrBuilder){
func(b *csrBuilder) {
},
}
testRecognizer(t, goodCases, isNodeClientCert, true)
testRecognizer(t, goodCases, isSelfNodeClientCert, true)
badCases := []func(b *csrBuilder){
func(b *csrBuilder) {
b.cn = "mike"
},
func(b *csrBuilder) {
b.orgs = nil
},
func(b *csrBuilder) {
b.orgs = []string{"system:master"}
},
func(b *csrBuilder) {
b.usages = append(b.usages, capi.UsageServerAuth)
},
}
testRecognizer(t, badCases, isNodeClientCert, false)
testRecognizer(t, badCases, isSelfNodeClientCert, false)
// cn different then requestor
differentCN := []func(b *csrBuilder){
func(b *csrBuilder) {
b.requestor = "joe"
},
func(b *csrBuilder) {
b.cn = "system:node:bar"
},
}
testRecognizer(t, differentCN, isNodeClientCert, true)
testRecognizer(t, differentCN, isSelfNodeClientCert, false)
}
func testRecognizer(t *testing.T, cases []func(b *csrBuilder), recognizeFunc func(csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool, shouldRecognize bool) {
for _, c := range cases {
b := csrBuilder{
cn: "system:node:foo",
orgs: []string{"system:nodes"},
requestor: "system:node:foo",
usages: []capi.KeyUsage{
capi.UsageKeyEncipherment,
capi.UsageDigitalSignature,
capi.UsageClientAuth,
},
}
c(&b)
t.Run(fmt.Sprintf("csr:%#v", b), func(t *testing.T) {
csr := makeFancyTestCsr(b)
x509cr, err := k8s_certificates_v1beta1.ParseCSR(csr)
if err != nil {
t.Errorf("unexpected err: %v", err)
}
if recognizeFunc(csr, x509cr) != shouldRecognize {
t.Errorf("expected recognized to be %v", shouldRecognize)
}
})
}
}
// noncryptographic for faster testing
// DO NOT COPY THIS CODE
var insecureRand = rand.New(rand.NewSource(0))
func makeTestCsr() *capi.CertificateSigningRequest {
return makeFancyTestCsr(csrBuilder{cn: "test-cert"})
}
type csrBuilder struct {
cn string
orgs []string
requestor string
usages []capi.KeyUsage
dns []string
emails []string
ips []net.IP
}
func makeFancyTestCsr(b csrBuilder) *capi.CertificateSigningRequest {
pk, err := ecdsa.GenerateKey(elliptic.P224(), insecureRand)
if err != nil {
panic(err)
}
csrb, err := x509.CreateCertificateRequest(insecureRand, &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: b.cn,
Organization: b.orgs,
},
DNSNames: b.dns,
EmailAddresses: b.emails,
IPAddresses: b.ips,
}, pk)
if err != nil {
panic(err)
}
return &capi.CertificateSigningRequest{
Spec: capi.CertificateSigningRequestSpec{
Username: b.requestor,
Usages: b.usages,
Request: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrb}),
},
}
}

View file

@ -0,0 +1,184 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package certificates implements an abstract controller that is useful for
// building controllers that manage CSRs
package certificates
import (
"fmt"
"time"
certificates "k8s.io/api/certificates/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
certificatesinformers "k8s.io/client-go/informers/certificates/v1beta1"
clientset "k8s.io/client-go/kubernetes"
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
certificateslisters "k8s.io/client-go/listers/certificates/v1beta1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue"
"k8s.io/kubernetes/pkg/controller"
"github.com/golang/glog"
)
type CertificateController struct {
kubeClient clientset.Interface
csrLister certificateslisters.CertificateSigningRequestLister
csrsSynced cache.InformerSynced
handler func(*certificates.CertificateSigningRequest) error
queue workqueue.RateLimitingInterface
}
func NewCertificateController(
kubeClient clientset.Interface,
csrInformer certificatesinformers.CertificateSigningRequestInformer,
handler func(*certificates.CertificateSigningRequest) error,
) (*CertificateController, error) {
// Send events to the apiserver
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(glog.Infof)
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.Core().RESTClient()).Events("")})
cc := &CertificateController{
kubeClient: kubeClient,
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "certificate"),
handler: handler,
}
// Manage the addition/update of certificate requests
csrInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
csr := obj.(*certificates.CertificateSigningRequest)
glog.V(4).Infof("Adding certificate request %s", csr.Name)
cc.enqueueCertificateRequest(obj)
},
UpdateFunc: func(old, new interface{}) {
oldCSR := old.(*certificates.CertificateSigningRequest)
glog.V(4).Infof("Updating certificate request %s", oldCSR.Name)
cc.enqueueCertificateRequest(new)
},
DeleteFunc: func(obj interface{}) {
csr, ok := obj.(*certificates.CertificateSigningRequest)
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
glog.V(2).Infof("Couldn't get object from tombstone %#v", obj)
return
}
csr, ok = tombstone.Obj.(*certificates.CertificateSigningRequest)
if !ok {
glog.V(2).Infof("Tombstone contained object that is not a CSR: %#v", obj)
return
}
}
glog.V(4).Infof("Deleting certificate request %s", csr.Name)
cc.enqueueCertificateRequest(obj)
},
})
cc.csrLister = csrInformer.Lister()
cc.csrsSynced = csrInformer.Informer().HasSynced
cc.handler = handler
return cc, nil
}
// Run the main goroutine responsible for watching and syncing jobs.
func (cc *CertificateController) Run(workers int, stopCh <-chan struct{}) {
defer utilruntime.HandleCrash()
defer cc.queue.ShutDown()
glog.Infof("Starting certificate controller")
defer glog.Infof("Shutting down certificate controller")
if !controller.WaitForCacheSync("certificate", stopCh, cc.csrsSynced) {
return
}
for i := 0; i < workers; i++ {
go wait.Until(cc.worker, time.Second, stopCh)
}
<-stopCh
}
// worker runs a thread that dequeues CSRs, handles them, and marks them done.
func (cc *CertificateController) worker() {
for cc.processNextWorkItem() {
}
}
// processNextWorkItem deals with one key off the queue. It returns false when it's time to quit.
func (cc *CertificateController) processNextWorkItem() bool {
cKey, quit := cc.queue.Get()
if quit {
return false
}
defer cc.queue.Done(cKey)
if err := cc.syncFunc(cKey.(string)); err != nil {
cc.queue.AddRateLimited(cKey)
utilruntime.HandleError(fmt.Errorf("Sync %v failed with : %v", cKey, err))
return true
}
cc.queue.Forget(cKey)
return true
}
func (cc *CertificateController) enqueueCertificateRequest(obj interface{}) {
key, err := controller.KeyFunc(obj)
if err != nil {
utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %+v: %v", obj, err))
return
}
cc.queue.Add(key)
}
// maybeSignCertificate will inspect the certificate request and, if it has
// been approved and meets policy expectations, generate an X509 cert using the
// cluster CA assets. If successful it will update the CSR approve subresource
// with the signed certificate.
func (cc *CertificateController) syncFunc(key string) error {
startTime := time.Now()
defer func() {
glog.V(4).Infof("Finished syncing certificate request %q (%v)", key, time.Now().Sub(startTime))
}()
csr, err := cc.csrLister.Get(key)
if errors.IsNotFound(err) {
glog.V(3).Infof("csr has been deleted: %v", key)
return nil
}
if err != nil {
return err
}
if csr.Status.Certificate != nil {
// no need to do anything because it already has a cert
return nil
}
// need to operate on a copy so we don't mutate the csr in the shared cache
csr = csr.DeepCopy()
return cc.handler(csr)
}

View file

@ -0,0 +1,86 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package certificates
import (
"testing"
"time"
certificates "k8s.io/api/certificates/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/kubernetes/pkg/controller"
)
// TODO flesh this out to cover things like not being able to find the csr in the cache, not
// auto-approving, etc.
func TestCertificateController(t *testing.T) {
csr := &certificates.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
Name: "test-csr",
},
}
client := fake.NewSimpleClientset(csr)
informerFactory := informers.NewSharedInformerFactory(fake.NewSimpleClientset(csr), controller.NoResyncPeriodFunc())
handler := func(csr *certificates.CertificateSigningRequest) error {
csr.Status.Conditions = append(csr.Status.Conditions, certificates.CertificateSigningRequestCondition{
Type: certificates.CertificateApproved,
Reason: "test reason",
Message: "test message",
})
_, err := client.Certificates().CertificateSigningRequests().UpdateApproval(csr)
if err != nil {
return err
}
return nil
}
controller, err := NewCertificateController(
client,
informerFactory.Certificates().V1beta1().CertificateSigningRequests(),
handler,
)
if err != nil {
t.Fatalf("error creating controller: %v", err)
}
controller.csrsSynced = func() bool { return true }
stopCh := make(chan struct{})
defer close(stopCh)
informerFactory.Start(stopCh)
informerFactory.WaitForCacheSync(stopCh)
wait.PollUntil(10*time.Millisecond, func() (bool, error) {
return controller.queue.Len() >= 1, nil
}, stopCh)
controller.processNextWorkItem()
actions := client.Actions()
if len(actions) != 1 {
t.Errorf("expected 1 actions")
}
if a := actions[0]; !a.Matches("update", "certificatesigningrequests") ||
a.GetSubresource() != "approval" {
t.Errorf("unexpected action: %#v", a)
}
}

View file

@ -0,0 +1,38 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package certificates
import certificates "k8s.io/api/certificates/v1beta1"
// IsCertificateRequestApproved returns true if a certificate request has the
// "Approved" condition and no "Denied" conditions; false otherwise.
func IsCertificateRequestApproved(csr *certificates.CertificateSigningRequest) bool {
approved, denied := GetCertApprovalCondition(&csr.Status)
return approved && !denied
}
func GetCertApprovalCondition(status *certificates.CertificateSigningRequestStatus) (approved bool, denied bool) {
for _, c := range status.Conditions {
if c.Type == certificates.CertificateApproved {
approved = true
}
if c.Type == certificates.CertificateDenied {
denied = true
}
}
return
}

View file

@ -0,0 +1,50 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["cfssl_signer_test.go"],
data = [
"testdata/ca.crt",
"testdata/ca.key",
"testdata/kubelet.csr",
],
library = ":go_default_library",
deps = [
"//vendor/k8s.io/api/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/util/cert:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["cfssl_signer.go"],
deps = [
"//pkg/controller/certificates:go_default_library",
"//vendor/github.com/cloudflare/cfssl/config:go_default_library",
"//vendor/github.com/cloudflare/cfssl/helpers:go_default_library",
"//vendor/github.com/cloudflare/cfssl/signer:go_default_library",
"//vendor/github.com/cloudflare/cfssl/signer/local:go_default_library",
"//vendor/k8s.io/api/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/informers/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,138 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package signer implements a CA signer that uses keys stored on local disk.
package signer
import (
"crypto"
"crypto/x509"
"fmt"
"io/ioutil"
"os"
"time"
capi "k8s.io/api/certificates/v1beta1"
certificatesinformers "k8s.io/client-go/informers/certificates/v1beta1"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/pkg/controller/certificates"
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/signer"
"github.com/cloudflare/cfssl/signer/local"
)
func NewCSRSigningController(
client clientset.Interface,
csrInformer certificatesinformers.CertificateSigningRequestInformer,
caFile, caKeyFile string,
certificateDuration time.Duration,
) (*certificates.CertificateController, error) {
signer, err := newCFSSLSigner(caFile, caKeyFile, client, certificateDuration)
if err != nil {
return nil, err
}
return certificates.NewCertificateController(
client,
csrInformer,
signer.handle,
)
}
type cfsslSigner struct {
ca *x509.Certificate
priv crypto.Signer
sigAlgo x509.SignatureAlgorithm
client clientset.Interface
certificateDuration time.Duration
}
func newCFSSLSigner(caFile, caKeyFile string, client clientset.Interface, certificateDuration time.Duration) (*cfsslSigner, error) {
ca, err := ioutil.ReadFile(caFile)
if err != nil {
return nil, fmt.Errorf("error reading CA cert file %q: %v", caFile, err)
}
cakey, err := ioutil.ReadFile(caKeyFile)
if err != nil {
return nil, fmt.Errorf("error reading CA key file %q: %v", caKeyFile, err)
}
parsedCa, err := helpers.ParseCertificatePEM(ca)
if err != nil {
return nil, fmt.Errorf("error parsing CA cert file %q: %v", caFile, err)
}
strPassword := os.Getenv("CFSSL_CA_PK_PASSWORD")
password := []byte(strPassword)
if strPassword == "" {
password = nil
}
priv, err := helpers.ParsePrivateKeyPEMWithPassword(cakey, password)
if err != nil {
return nil, fmt.Errorf("Malformed private key %v", err)
}
return &cfsslSigner{
priv: priv,
ca: parsedCa,
sigAlgo: signer.DefaultSigAlgo(priv),
client: client,
certificateDuration: certificateDuration,
}, nil
}
func (s *cfsslSigner) handle(csr *capi.CertificateSigningRequest) error {
if !certificates.IsCertificateRequestApproved(csr) {
return nil
}
csr, err := s.sign(csr)
if err != nil {
return fmt.Errorf("error auto signing csr: %v", err)
}
_, err = s.client.Certificates().CertificateSigningRequests().UpdateStatus(csr)
if err != nil {
return fmt.Errorf("error updating signature for csr: %v", err)
}
return nil
}
func (s *cfsslSigner) sign(csr *capi.CertificateSigningRequest) (*capi.CertificateSigningRequest, error) {
var usages []string
for _, usage := range csr.Spec.Usages {
usages = append(usages, string(usage))
}
policy := &config.Signing{
Default: &config.SigningProfile{
Usage: usages,
Expiry: s.certificateDuration,
ExpiryString: s.certificateDuration.String(),
},
}
cfs, err := local.NewSigner(s.priv, s.ca, s.sigAlgo, policy)
if err != nil {
return nil, err
}
csr.Status.Certificate, err = cfs.Sign(signer.SignRequest{
Request: string(csr.Spec.Request),
})
if err != nil {
return nil, err
}
return csr, nil
}

View file

@ -0,0 +1,84 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package signer
import (
"crypto/x509"
"io/ioutil"
"reflect"
"testing"
"time"
capi "k8s.io/api/certificates/v1beta1"
"k8s.io/client-go/util/cert"
)
func TestSigner(t *testing.T) {
s, err := newCFSSLSigner("./testdata/ca.crt", "./testdata/ca.key", nil, 1*time.Hour)
if err != nil {
t.Fatalf("failed to create signer: %v", err)
}
csrb, err := ioutil.ReadFile("./testdata/kubelet.csr")
if err != nil {
t.Fatalf("failed to read CSR: %v", err)
}
csr := &capi.CertificateSigningRequest{
Spec: capi.CertificateSigningRequestSpec{
Request: []byte(csrb),
Usages: []capi.KeyUsage{
capi.UsageSigning,
capi.UsageKeyEncipherment,
capi.UsageServerAuth,
capi.UsageClientAuth,
},
},
}
csr, err = s.sign(csr)
if err != nil {
t.Fatalf("failed to sign CSR: %v", err)
}
certData := csr.Status.Certificate
if len(certData) == 0 {
t.Fatalf("expected a certificate after signing")
}
certs, err := cert.ParseCertsPEM(certData)
if err != nil {
t.Fatalf("failed to parse certificate: %v", err)
}
if len(certs) != 1 {
t.Fatalf("expected one certificate")
}
crt := certs[0]
if crt.Subject.CommonName != "system:node:k-a-node-s36b" {
t.Errorf("expected common name of 'system:node:k-a-node-s36b', but got: %v", certs[0].Subject.CommonName)
}
if !reflect.DeepEqual(crt.Subject.Organization, []string{"system:nodes"}) {
t.Errorf("expected organization to be [system:nodes] but got: %v", crt.Subject.Organization)
}
if crt.KeyUsage != x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment {
t.Errorf("bad key usage")
}
if !reflect.DeepEqual(crt.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) {
t.Errorf("bad extended key usage")
}
}

View file

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC9zCCAd+gAwIBAgIJAOWJ8tWNUIsZMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV
BAMMB2t1YmUtY2EwHhcNMTYxMjIyMDAyNTI5WhcNNDQwNTA5MDAyNTI5WjASMRAw
DgYDVQQDDAdrdWJlLWNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
1HK1d2p7N7UC6px8lVtABw8jPpVyNYjrJmI+TKTTdCgWGsUTFMCw4t4Q/KQDDlvB
P19uPhbfp8aLwOWXBCxOPZzlM2mAEjSUgKjbyGCW/8vaXa2VgQm3tKZdydKiFvIo
fEsNA+58w8A0WWEB8wYFcdCt8uPyQ0ws/TxE+WW3u7EPlC0/inIX9JqeZZMpDk3N
lHEv/pGEjQmoet/hBwGHq9PKepkN5/V6rrSADJ5I4Uklp2f7G9MCP/zV8xKfs0lK
CMoJsIPK3nL9N3C0rqBQPfcyKE2fnEkxC3UVZA8brvLTkBfOgmM2eVg/nauU1ejv
zOJL7tDwUioLriw2hiGrFwIDAQABo1AwTjAdBgNVHQ4EFgQUbGJxJeW7BgZ4xSmW
d3Aw3gq8YZUwHwYDVR0jBBgwFoAUbGJxJeW7BgZ4xSmWd3Aw3gq8YZUwDAYDVR0T
BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAunzpYAxpzguzxG83pK5n3ObsGDwO
78d38qX1VRvMLPvioZxYgquqqFPdLI3xe8b8KdZNzb65549tgjAI17tTKGTRgJu5
yzLU1tO4vNaAFecMCtPvElYfkrAv2vbGCVJ1bYKTnjdu3083jG3sY9TDj0364A57
lNwKEd5uxHGWg4H+NbyHkDqfKmllzLvJ9XjSWBPmNVLSW50hV+h9fUXgz9LN+qVY
VEDfAEWqb6PVy9ANw8A8QLnuSRxbd7hAigtlC4MwzYJ6tyFIIH6bCIgfoZuA+brm
WGcpIxl4fKEGafSgjsK/6Yhb61mkhHmG16mzEUZNkNsjiYJuF2QxpOlQrw==
-----END CERTIFICATE-----

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA1HK1d2p7N7UC6px8lVtABw8jPpVyNYjrJmI+TKTTdCgWGsUT
FMCw4t4Q/KQDDlvBP19uPhbfp8aLwOWXBCxOPZzlM2mAEjSUgKjbyGCW/8vaXa2V
gQm3tKZdydKiFvIofEsNA+58w8A0WWEB8wYFcdCt8uPyQ0ws/TxE+WW3u7EPlC0/
inIX9JqeZZMpDk3NlHEv/pGEjQmoet/hBwGHq9PKepkN5/V6rrSADJ5I4Uklp2f7
G9MCP/zV8xKfs0lKCMoJsIPK3nL9N3C0rqBQPfcyKE2fnEkxC3UVZA8brvLTkBfO
gmM2eVg/nauU1ejvzOJL7tDwUioLriw2hiGrFwIDAQABAoIBAFJCmEFE2bEYRajS
LusmCgSxt9PjyfUwrtyN7dF/gODZJLX42QqQEe3GTo2EdCp7HLiNGwKvmKo+Fp76
Rx82iJUSyyy9DPn/ogCvYWqU++LP7B2ZuOnd+WPZhzc+d8Sqv0JhTQjYrzaclaiG
B1syWalYRAJogMXOGR102MA4wovJrlHFuTVSWiDe0uguLxyjoTMIRqbib9ZAMSLX
bfcM2abGpXgq10abda3KKAJbZyr2fnBvqKTs4a4zYeHJpQT+NBPMiryb2WnPFg+b
93nrjDxUtPsx8NJz6HGkSQLagXkZX2J1JpT8loaNIdyQHab1LNXptc84LR8xxusy
bs5NowECgYEA+j+SwVgeC+NCUIfxr3F9zPAD9A0Tk3gD4z+j0opfLIMghX4jtK0e
9fQyglecAbojlkEUk/js5IVZ0IIhBNPWXxKtdShZO7EmJ6Z5IEmFrZK1xUomYBa2
BfysqSAkxVLsTDIfI0Q4DHQNDOV+iY3j8WoaR51cXr+IY+mYBGSNI80CgYEA2VS5
X5QHDxoh3r5ORiyab3ciubEofJ29D3NR1tCe9ZgSYRV5Y7T/4KPpZdpsEX/ydYD6
X4DyURuYNK7PUR8DSlX7/VuMzHThqGJMaT0LE+alU4bruiad33X1WXgtcPTGCic0
8il50TZTgba0CwxuCO1eVb3IijwgJBX/byM67nMCgYEA7As1KSwtwzbMoVtpa/xY
Fgu7HuOKuIn22M55fylH1puk/GXb1huJ3aNGVU2/+J0T3jFq8JxXDsJ90kA8Vupe
BXV/qceyS6yv+ax8Cilvbya4T+y+P9qMPR912V1Zccri2ohYeJJrb8uzV5vM/ICb
JmbXfP+AVlrBksSOwG37920CgYEAsSi2X6o8QtxLhdZd2ihbz8cu4G4AkezHhAO+
T70KBytquAcYR+Xwu38CMEvn0jAZRh3YeueTH/i9jxx81STRutPysSni0Xvpwyg2
H4dqM1PNqxQNrlXyVYlDciZb7HsrwHULXOfgbGG7mr6Db4o3XEGap4woID84+BGS
glcWn+8CgYEA36uulmZcodfet04qQvlDtr1d7mwLdTR/JAO0ZBIgFH7eGZdEVh8O
DoTJTdSSJGiv8J35PwEXfhKHjhgOjDocLYu+yCOwVj7jRdHqlDS1BaE36Hzdw0rb
mWkBRMGJtGhzhoRJEFHAnoLXc9danRfnHwVR58drlf7bjR5I9eU9u1I=
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,8 @@
-----BEGIN CERTIFICATE REQUEST-----
MIH1MIGdAgEAMDsxFTATBgNVBAoTDHN5c3RlbTpub2RlczEiMCAGA1UEAxMZc3lz
dGVtOm5vZGU6ay1hLW5vZGUtczM2YjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA
BJbxa5Y8SrUJVHpOoWD5ceqH+5R9mjIhwVP2sqfTcLkjvbitzOiLlxSq/LwJ+qq7
kVpf9f3GopZVhRWbYSCg0YGgADAKBggqhkjOPQQDAgNHADBEAiAabb6XFtPOJUCQ
+84NhxLEvPANhrtwFq3Q0qFZ9TzH5QIgc/697RTTcbri2lVj+10dLFIC3VYJ7br4
QjA7haCYXrA=
-----END CERTIFICATE REQUEST-----