mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-25 19:31:05 +00:00
Enable Vault auth through kubernetes auth method
Added a feature to obtain the initial Vault token from the Kubernetes auth method. This works by making a request to the Vault server at the specified auth method mount point's login path and presenting the JWT located in a file on a running pod, along with the Kubernetes role to authenticate as. Vault will then respond with a token and its TTL, if the request is valid.
This commit is contained in:
parent
9d8f8c3a44
commit
79428aa231
5 changed files with 158 additions and 1 deletions
|
@ -175,6 +175,24 @@ var flags = []cli.Flag{
|
|||
Usage: "token to secure prometheus metrics endpoint",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DRONE_VAULT_AUTH_TYPE",
|
||||
Name: "drone-vault-auth-type",
|
||||
Usage: "auth backend type used for connecting to vault",
|
||||
Value: "token",
|
||||
},
|
||||
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
|
||||
//
|
||||
|
|
1
plugins/secrets/vault/fixtures/fakeJwt
Normal file
1
plugins/secrets/vault/fixtures/fakeJwt
Normal file
|
@ -0,0 +1 @@
|
|||
fakeJwt
|
47
plugins/secrets/vault/kubernetes.go
Normal file
47
plugins/secrets/vault/kubernetes.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
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, mountPoint, 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, mountPoint)
|
||||
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)
|
||||
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())
|
||||
}
|
||||
}
|
|
@ -4,7 +4,11 @@
|
|||
|
||||
package vault
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Opts sets custom options for the vault client.
|
||||
type Opts func(v *vault)
|
||||
|
@ -24,3 +28,21 @@ func WithRenewal(d time.Duration) Opts {
|
|||
v.renew = d
|
||||
}
|
||||
}
|
||||
|
||||
func WithKubernetesAuth() Opts {
|
||||
return func(v *vault) {
|
||||
addr := os.Getenv("VAULT_ADDR")
|
||||
role := os.Getenv("DRONE_VAULT_KUBERNETES_ROLE")
|
||||
mount := os.Getenv("DRONE_VAULT_AUTH_MOUNT_POINT")
|
||||
jwtFile := "/var/run/secrets/kubernetes.io/serviceaccount/token"
|
||||
token, ttl, err := getKubernetesToken(addr, role, mount, jwtFile)
|
||||
if err != nil {
|
||||
logrus.Debugf("vault: failed to obtain token via kubernetes-auth backend: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
v.client.SetToken(token)
|
||||
v.ttl = ttl
|
||||
v.renew = ttl / 2
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue