Skip to content

Commit

Permalink
Add tests, docs and update Dockerfile
Browse files Browse the repository at this point in the history
  • Loading branch information
Adam Talbot committed Jun 17, 2018
1 parent 88ac79b commit f0d6f0b
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 21 deletions.
14 changes: 13 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,16 @@ jobs:
- stage: test
script: make test
- stage: build
script: make bin/darwin_amd64/k8s-vault-csr bin/linux_amd64/k8s-vault-csr bin/linux_arm64/k8s-vault-csr bin/linux_arm/k8s-vault-csr
script: make bin/darwin_amd64/k8s-vault-csr bin/linux_amd64/k8s-vault-csr bin/linux_arm64/k8s-vault-csr bin/linux_arm/k8s-vault-csr
deploy:
provider: releases
api_key:
secure: "GpcAkFWnFtCMCbQCW/KUbsfJVyp97WWzdigcrMD1yTqz2g4Yv4/ElbUT6UG7s90JhYlMtislaKuI02Z6hUnCcPde8M/Lu06gxgW67FZ2GAI/xFHFIh5qXkjiC4y/77JkUjHcFSc/54TiN35i49U7GlKHJZU289Svr5CC+7c69HuZ37pPkflRYagUnghCZ0tK4pzxVk3ZyIwvoiswmp0eLysz+Ng8azGxdbRFNOyYAP3V3pOyH3xuuhjQ/3iJ5p/PyDTmKZWt7UCcGW5vVEuHfrjUyxm/mF388tIkNwOvKJn2bVcm01QesS4jbYxvk8krg2F0mW0uvg9PaWojkuWsNLh0k7ZuHiTm+KcHFMXFc3cgGHP7EQtN7UfjQj59LUC3fI3iRbVaKeg9wexPFqh05Ompe3ls5krsK27TvYBxNFqRPbO18+DWQIicZSvWT/wPAcxbWbd35JHWJysAS7vDhiTPWLCsFV1yEPjKGs4tDH1AvE3LZ9tBNYspNfbnYr2fkYYCbCxEb2iOUGJ8SDL3XDXFxb1hzGV3J2XzBbjgHwSPXsE46aVG5S/58akCGSfdMVVNzmuVj1PwXpLpLkkGeKHMQqN5ZwwuiNYJOlspcf4JIBsfXNTS7HcCih9pVTDY4Xj2JnRXT5WmHXXZYXNCDzEoT48asBZI+9cXHD4ZGMg="
file:
- bin/darwin_amd64/k8s-vault-csr
- bin/linux_amd64/k8s-vault-csr
- bin/linux_arm64/k8s-vault-csr
- bin/linux_arm/k8s-vault-csr
skip_cleanup: true
on:
tags: true
20 changes: 13 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
FROM golang:1.10.3-alpine as build
FROM golang:1.10.3 as build

RUN apk add --no-cache git openssh ca-certificates
RUN wget -O - https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
ARG DEP_VERSION=0.4.1

# install dep
RUN wget -O /usr/local/bin/dep https://github.com/golang/dep/releases/download/v${DEP_VERSION}/dep-linux-amd64 && \
chmod +x /usr/local/bin/dep

# add code
ADD . /go/src/github.com/thatsmrtalbot/k8s-vault-csr
WORKDIR /go/src/github.com/thatsmrtalbot/k8s-vault-csr
RUN dep ensure -vendor-only -v
RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o /bin/k8s-vault-csr ./cmd/k8s-vault-csr

FROM scratch
# vendor and build
RUN make bin/linux_amd64/k8s-vault-csr USE_DOCKER=0 VENDOR_ONLY=1

COPY --from=build /bin/k8s-vault-csr /bin/k8s-vault-csr
# final container
FROM scratch
COPY --from=build /go/src/github.com/thatsmrtalbot/k8s-vault-csr/bin/linux_amd64/k8s-vault-csr /bin/k8s-vault-csr
ENTRYPOINT [ "/bin/k8s-vault-csr" ]
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
[![Build Status](https://travis-ci.org/ThatsMrTalbot/k8s-vault-csr.svg?branch=master)](https://travis-ci.org/ThatsMrTalbot/k8s-vault-csr)

# Kubernetes CSR Vault Controller

Kubernetes supports the approval and signing of x509 Certificate Signing Requests. This can be used internally by Kubernetes for things such as kubelet client certificate rotation. Typically CSRs are signed by the controller-manager with a provided CA and key.
Expand All @@ -12,3 +14,50 @@ This controller uses much of the same code as the default Kubernetes CSR signer,

The latest release of Vault (0.10.2) does not allow a client to specify "key usage" or "extended key usage" when using the `sign-verbatim` endpoint. This has been solved in master (https://github.com/hashicorp/vault/pull/4777) and should be in Vault 0.10.3

## Installing

`k8s-vault-csr` can run in cluster or standalone. The fastest path is to run in cluster:

- The default `csrsigning` controller first need disabling in the controller manager. This can be done through the command line flag `--controllers`, for example `--controllers=-csrsigning`
- If you want to use kubernetes auth in vault then this needs setting up, the signer needs permission to call `/pki/sign-verbatim/role` where `pki` and `role` are the pki mount and role respectively.
- Deploy `kube-vault-signer` and RBAC. See `deploy.yaml` for an example.

## Flags

```
Usage of k8s-vault-csr:
-alsologtostderr
log to standard error as well as files
-kubeconfig string
kubeconfig file to use
-log_backtrace_at value
when logging hits line file:N, emit a stack trace
-log_dir string
If non-empty, write log files in this directory
-logtostderr
log to standard error instead of files
-master string
kubernetes mastere url
-mount string
specify the pki mount to use to generate certificates (default "pki")
-role string
specify role to use, only ttl is used from the role
-service-token-file string
file to load service token from (default "/var/run/secrets/kubernetes.io/serviceaccount")
-service-token-mount string
name of the kubernetes auth mount in vault (default "kubernetes")
-service-token-role string
role to use when authenticating with vault using the service token
-stderrthreshold value
logs at or above this threshold go to stderr
-use-service-token
use service token vault authentication
-v value
log level for V logs
-vault-address string
vault server address
-vmodule value
comma-separated list of pattern=N settings for file-filtered logging
-workers int
number of workers to run (default 4)
```
12 changes: 6 additions & 6 deletions cmd/k8s-vault-csr/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ var (
masterAddr = flag.String("master", "", "kubernetes mastere url")
kubeconfig = flag.String("kubeconfig", "", "kubeconfig file to use")
vaultAddr = flag.String("vault-address", "", "vault server address")
useServiceToken = flag.Bool("use-service-token", false, "use service token vault authentication")
serviceTokenMount = flag.String("service-token-mount", "kubernetes", "name of the kubernetes auth mount in vault")
serviceTokenRole = flag.String("service-token-role", "", "role to use when authenticating with vault using the service token")
useServiceToken = flag.Bool("use-kubernetes-auth", false, "use service token vault authentication")
serviceTokenMount = flag.String("kubernetes-auth-mount", "kubernetes", "name of the kubernetes auth mount in vault")
serviceTokenRole = flag.String("kubernetes-auth-role", "", "role to use when authenticating with vault using the service token")
serviceTokenFile = flag.String("service-token-file", "/var/run/secrets/kubernetes.io/serviceaccount", "file to load service token from")
workers = flag.Int("workers", 4, "number of workers to run")
pkiMount = flag.String("mount", "pki", "specify the pki mount to use to generate certificates")
pkiRole = flag.String("role", "", "specify role to use, only ttl is used from the role")
workers = flag.Int("signer-workers", 4, "number of workers to run")
pkiMount = flag.String("vault-pki-mount", "pki", "specify the pki mount to use to generate certificates")
pkiRole = flag.String("vault-pki-role", "", "specify role to use, only ttl is used from the role")
)

func init() {
Expand Down
61 changes: 61 additions & 0 deletions deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: kube-vault-signer
namespace: kube-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: kube-vault-signer
rules:
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
- certificatesigningrequests/status
verbs:
- get
- list
- watch
- patch
- update
---
kind: CluterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: kube-vault-signer
subjects:
- kind: ServiceAccount
name: kube-vault-signer
namespace: kube-system
roleRef:
kind: ClusterRole
name: kube-vault-signer
apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kube-vault-signer
namespace: kube-system
labels:
k8s-app: kube-vault-signer
spec:
replicas: 1
selector:
matchLabels:
k8s-app: kube-vault-signer
template:
metadata:
labels:
k8s-app: kube-vault-signer
spec:
serviceAccountName: kube-vault-signer
containers:
- name: kube-vault-signer
image: thatsmrtalbot/kube-vault-signer:0.0.1
args:
- -vault-address=https://vault.example.com
- -use-kubernetes-auth=true
- -kubernetes-auth-role=kube-vault-signer
15 changes: 9 additions & 6 deletions pkg/vault/token/renewer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package token

import (
"time"
"encoding/json"
"github.com/golang/glog"
"github.com/pkg/errors"
"github.com/hashicorp/vault/api"
Expand All @@ -10,6 +11,8 @@ import (

var ErrNoAuthProvider = errors.New("no vault authentication method provided")

var infinity = time.Duration(^uint64(0) >> 1)

func NewRenewer(client *api.Client, authFn func(*api.Client) error) *Renewer {
if authFn == nil {
authFn = func(*api.Client) error {
Expand All @@ -20,7 +23,6 @@ func NewRenewer(client *api.Client, authFn func(*api.Client) error) *Renewer {
return &Renewer{
client: client,
authFn: authFn,
err: make(chan error),
}
}

Expand All @@ -41,6 +43,11 @@ func (r *Renewer) currentTokenStatus() (*tokenStatus, error) {
return nil, err
}

ttl, err := secret.Data["ttl"].(json.Number).Int64()
if err != nil {
return nil, err
}

if time.Now().UTC().After(expires) {
return &tokenStatus{
HasToken: true,
Expand All @@ -51,7 +58,7 @@ func (r *Renewer) currentTokenStatus() (*tokenStatus, error) {
return &tokenStatus{
HasToken: true,
ExpiresIn: time.Now().UTC().Sub(expires),
TTL: time.Duration(secret.Data["creation_ttl"].(int)) * time.Second,
TTL: time.Duration(ttl) * time.Second,
}, nil
}

Expand Down Expand Up @@ -89,10 +96,6 @@ func (r *Renewer) tick() error {
return nil
}

func (r *Renewer) Error() <-chan error {
return r.err
}

func (r *Renewer) Run(done <-chan struct{}) error {
ticker := time.NewTicker(time.Second)
for {
Expand Down
108 changes: 108 additions & 0 deletions pkg/vault/token/renewer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package token

import (
"context"
"testing"

log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/helper/logging"
"github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/physical/inmem"
"github.com/hashicorp/vault/vault"
)

func TestRenewer(t *testing.T) {
// Set up vault

logger := logging.NewVaultLogger(log.Trace)

phys, err := inmem.NewInmem(nil, logger)
if err != nil {
t.Fatal(err)
return
}

core, err := vault.NewCore(&vault.CoreConfig{
Physical: phys,
LogicalBackends: map[string]logical.Factory{},
DisableMlock: true,
})

if err != nil {
t.Fatal("error initializing core: ", err)
return
}

init, err := core.Initialize(context.Background(), &vault.InitParams{
BarrierConfig: &vault.SealConfig{
SecretShares: 1,
SecretThreshold: 1,
},
RecoveryConfig: nil,
})

if err != nil {
t.Fatal("error initializing core: ", err)
return
}

if unsealed, err := core.Unseal(init.SecretShares[0]); err != nil {
t.Fatal("error unsealing core: ", err)
return
} else if !unsealed {
t.Fatal("vault shouldn't be sealed")
return
}

ln, addr := http.TestServer(nil, core)
defer ln.Close()

clientConfig := api.DefaultConfig()
clientConfig.Address = addr
client, err := api.NewClient(clientConfig)

if err != nil {
t.Fatal("error initializing HTTP client: ", err)
return
}

client.SetToken(init.RootToken)

// Set token

secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
TTL: "3600",
})

if err != nil {
t.Fatal("error creating child token: ", err)
return
}

client.SetToken(secret.Auth.ClientToken)

// Test case

renewer := NewRenewer(client, nil)
status, err := renewer.currentTokenStatus()

if err != nil {
t.Errorf("error getting token status: %s", err)
}

if !status.HasToken {
t.Error("no token")
}

if status.Expired {
t.Error("token is expired")
}

err = renewer.renew()

if err != nil {
t.Errorf("error renewing token: %s", err)
}
}
1 change: 0 additions & 1 deletion pkg/vault/token/type.go → pkg/vault/token/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ type tokenStatus struct {
}

type Renewer struct {
err chan error
client *api.Client
authFn func(*api.Client) error
}

0 comments on commit f0d6f0b

Please sign in to comment.