Merge pull request #2406 from praxist/kubernetes-auth
Enable Vault auth through kubernetes auth method
This commit is contained in:
commit
d6f595e721
7 changed files with 221 additions and 5 deletions
|
@ -175,6 +175,24 @@ var flags = []cli.Flag{
|
||||||
Usage: "token to secure prometheus metrics endpoint",
|
Usage: "token to secure prometheus metrics endpoint",
|
||||||
Value: "",
|
Value: "",
|
||||||
},
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
EnvVar: "DRONE_VAULT_AUTH_TYPE",
|
||||||
|
Name: "drone-vault-auth-type",
|
||||||
|
Usage: "auth backend type used for connecting to vault",
|
||||||
|
Value: "",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
EnvVar: "DRONE_VAULT_AUTH_MOUNT_POINT",
|
||||||
|
Name: "drone-vault-auth-mount-point",
|
||||||
|
Usage: "mount point for desired vault auth backend",
|
||||||
|
Value: "",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
EnvVar: "DRONE_VAULT_KUBERNETES_ROLE",
|
||||||
|
Name: "drone-vault-kubernetes-role",
|
||||||
|
Usage: "role to authenticate as for vault kubernetes auth",
|
||||||
|
Value: "",
|
||||||
|
},
|
||||||
//
|
//
|
||||||
// resource limit parameters
|
// resource limit parameters
|
||||||
//
|
//
|
||||||
|
|
1
plugins/secrets/vault/fixtures/fakeJwt
Normal file
1
plugins/secrets/vault/fixtures/fakeJwt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
fakeJwt
|
51
plugins/secrets/vault/kubernetes.go
Normal file
51
plugins/secrets/vault/kubernetes.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package vault
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/drone/drone/plugins/internal"
|
||||||
|
"io/ioutil"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Vault JSON Response
|
||||||
|
{
|
||||||
|
"auth": {
|
||||||
|
"client_token" = "token",
|
||||||
|
"lease_duration" = "1234s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
type vaultAuth struct {
|
||||||
|
Token string `json:"client_token"`
|
||||||
|
Lease string `json:"lease_duration"`
|
||||||
|
}
|
||||||
|
type vaultResp struct {
|
||||||
|
Auth vaultAuth
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKubernetesToken(addr, role, mount, tokenFile string) (string, time.Duration, error) {
|
||||||
|
b, err := ioutil.ReadFile(tokenFile)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp vaultResp
|
||||||
|
path := fmt.Sprintf("%s/v1/auth/%s/login", addr, mount)
|
||||||
|
data := map[string]string{
|
||||||
|
"jwt": string(b),
|
||||||
|
"role": role,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = internal.Send("POST", path, data, &resp)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ttl, err := time.ParseDuration(resp.Auth.Lease)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Auth.Token, ttl, nil
|
||||||
|
}
|
69
plugins/secrets/vault/kubernetes_test.go
Normal file
69
plugins/secrets/vault/kubernetes_test.go
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
package vault
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetKubernetesToken(t *testing.T) {
|
||||||
|
fakeRole := "fakeRole"
|
||||||
|
fakeMountPoint := "kubernetes"
|
||||||
|
fakeJwtFile := "fixtures/fakeJwt"
|
||||||
|
b, _ := ioutil.ReadFile(fakeJwtFile)
|
||||||
|
fakeJwt := string(b)
|
||||||
|
fakeClientToken := "fakeClientToken"
|
||||||
|
fakeLeaseMinutes := "10m"
|
||||||
|
fakeLeaseDuration, _ := time.ParseDuration(fakeLeaseMinutes)
|
||||||
|
fakeResp := fmt.Sprintf("{\"auth\": {\"client_token\": \"%s\", \"lease_duration\": \"%s\"}}", fakeClientToken, fakeLeaseMinutes)
|
||||||
|
expectedPath := "/v1/auth/kubernetes/login"
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
if r.Method != "POST" {
|
||||||
|
t.Errorf("Expected 'POST' request, got '%s'", r.Method)
|
||||||
|
}
|
||||||
|
if r.URL.EscapedPath() != expectedPath {
|
||||||
|
t.Errorf("Expected request to '%s', got '%s'", expectedPath, r.URL.EscapedPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
var postdata struct {
|
||||||
|
Jwt string
|
||||||
|
Role string
|
||||||
|
}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&postdata)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Encountered error parsing request JSON: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jwt := postdata.Jwt
|
||||||
|
|
||||||
|
if jwt != fakeJwt {
|
||||||
|
t.Errorf("Expected request to have jwt with value '%s', got: '%s'", fakeJwt, jwt)
|
||||||
|
}
|
||||||
|
role := postdata.Role
|
||||||
|
if role != fakeRole {
|
||||||
|
t.Errorf("Expected request to have role with value '%s', got: '%s'", fakeRole, role)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, fakeResp)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
url := ts.URL
|
||||||
|
token, ttl, err := getKubernetesToken(url, fakeRole, fakeMountPoint, fakeJwtFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("getKubernetesToken returned an error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if token != fakeClientToken {
|
||||||
|
t.Errorf("Expected returned token to have value '%s', got: '%s'", fakeClientToken, token)
|
||||||
|
}
|
||||||
|
if ttl != fakeLeaseDuration {
|
||||||
|
t.Errorf("Expected TTL to have value '%s', got: '%s'", fakeLeaseDuration.Seconds(), ttl.Seconds())
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,3 +24,22 @@ func WithRenewal(d time.Duration) Opts {
|
||||||
v.renew = d
|
v.renew = d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithAuth returns an options that sets the vault
|
||||||
|
// method to use for authentication
|
||||||
|
func WithAuth(method string) Opts {
|
||||||
|
return func(v *vault) {
|
||||||
|
v.auth = method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithKubernetes returns an options that sets
|
||||||
|
// kubernetes-auth parameters required to retrieve
|
||||||
|
// an initial vault token
|
||||||
|
func WithKubernetesAuth(addr, role, mount string) Opts {
|
||||||
|
return func(v *vault) {
|
||||||
|
v.kubeAuth.addr = addr
|
||||||
|
v.kubeAuth.role = role
|
||||||
|
v.kubeAuth.mount = mount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -26,3 +26,31 @@ func TestWithRenewal(t *testing.T) {
|
||||||
t.Errorf("Want renewal %v, got %v", want, got)
|
t.Errorf("Want renewal %v, got %v", want, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWithAuth(t *testing.T) {
|
||||||
|
v := new(vault)
|
||||||
|
method := "kubernetes"
|
||||||
|
opt := WithAuth(method)
|
||||||
|
opt(v)
|
||||||
|
if got, want := v.auth, method; got != want {
|
||||||
|
t.Errorf("Want auth %v, got %v", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithKubernetesAuth(t *testing.T) {
|
||||||
|
v := new(vault)
|
||||||
|
addr := "https://address.fake"
|
||||||
|
role := "fakeRole"
|
||||||
|
mount := "kubernetes"
|
||||||
|
opt := WithKubernetesAuth(addr, role, mount)
|
||||||
|
opt(v)
|
||||||
|
if got, want := v.kubeAuth.addr, addr; got != want {
|
||||||
|
t.Errorf("Want addr %v, got %v", want, got)
|
||||||
|
}
|
||||||
|
if got, want := v.kubeAuth.role, role; got != want {
|
||||||
|
t.Errorf("Want role %v, got %v", want, got)
|
||||||
|
}
|
||||||
|
if got, want := v.kubeAuth.mount, mount; got != want {
|
||||||
|
t.Errorf("Want mount %v, got %v", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -45,9 +45,15 @@ type vault struct {
|
||||||
client *api.Client
|
client *api.Client
|
||||||
ttl time.Duration
|
ttl time.Duration
|
||||||
renew time.Duration
|
renew time.Duration
|
||||||
|
auth string
|
||||||
|
kubeAuth kubeAuth
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type kubeAuth struct {
|
||||||
|
addr, role, mount string
|
||||||
|
}
|
||||||
|
|
||||||
// New returns a new store with secrets loaded from vault.
|
// New returns a new store with secrets loaded from vault.
|
||||||
func New(store model.ConfigStore, opts ...Opts) (secrets.Plugin, error) {
|
func New(store model.ConfigStore, opts ...Opts) (secrets.Plugin, error) {
|
||||||
client, err := api.NewClient(nil)
|
client, err := api.NewClient(nil)
|
||||||
|
@ -61,10 +67,34 @@ func New(store model.ConfigStore, opts ...Opts) (secrets.Plugin, error) {
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(v)
|
opt(v)
|
||||||
}
|
}
|
||||||
|
if v.auth == "kubernetes" {
|
||||||
|
err = v.initKubernetes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
v.start() // start the refresh process.
|
v.start() // start the refresh process.
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *vault) initKubernetes() error {
|
||||||
|
token, ttl, err := getKubernetesToken(
|
||||||
|
v.kubeAuth.addr,
|
||||||
|
v.kubeAuth.role,
|
||||||
|
v.kubeAuth.mount,
|
||||||
|
"/var/run/secrets/kubernetes.io/serviceaccount/token",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("vault: failed to obtain token via kubernetes-auth backend: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
v.client.SetToken(token)
|
||||||
|
v.ttl = ttl
|
||||||
|
v.renew = ttl / 2
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (v *vault) SecretListBuild(repo *model.Repo, build *model.Build) ([]*model.Secret, error) {
|
func (v *vault) SecretListBuild(repo *model.Repo, build *model.Build) ([]*model.Secret, error) {
|
||||||
return v.list(repo, build)
|
return v.list(repo, build)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue