diff --git a/config/policy.go b/config/policy.go index 72d7cecd2..ea57f2f27 100644 --- a/config/policy.go +++ b/config/policy.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "io/ioutil" "net/url" "os" "time" @@ -105,6 +106,8 @@ type Policy struct { // KubernetesServiceAccountToken is the kubernetes token to use for upstream requests. KubernetesServiceAccountToken string `mapstructure:"kubernetes_service_account_token" yaml:"kubernetes_service_account_token,omitempty"` + // KubernetesServiceAccountTokenFile contains the kubernetes token to use for upstream requests. + KubernetesServiceAccountTokenFile string `mapstructure:"kubernetes_service_account_token_file" yaml:"kubernetes_service_account_token_file,omitempty"` // EnableGoogleCloudServerlessAuthentication adds "Authorization: Bearer ID_TOKEN" headers // to upstream requests. @@ -267,6 +270,18 @@ func (p *Policy) Validate() error { } } + if p.KubernetesServiceAccountTokenFile != "" { + if p.KubernetesServiceAccountToken != "" { + return fmt.Errorf("config: specified both `kubernetes_service_account_token_file` and `kubernetes_service_account_token`") + } + + token, err := ioutil.ReadFile(p.KubernetesServiceAccountTokenFile) + if err != nil { + return fmt.Errorf("config: failed to load kubernetes service account token: %w", err) + } + p.KubernetesServiceAccountToken = string(token) + } + return nil } diff --git a/config/policy_test.go b/config/policy_test.go index 2edb26bf9..10054484b 100644 --- a/config/policy_test.go +++ b/config/policy_test.go @@ -38,6 +38,10 @@ func Test_PolicyValidate(t *testing.T) { {"bad certificate file", Policy{From: "https://httpbin.corp.example", To: "https://httpbin.corp.notatld", TLSClientCertFile: "testdata/example-cert-404.pem", TLSClientKeyFile: "testdata/example-key.pem"}, true}, {"bad key file", Policy{From: "https://httpbin.corp.example", To: "https://httpbin.corp.notatld", TLSClientCertFile: "testdata/example-cert.pem", TLSClientKeyFile: "testdata/example-key-404.pem"}, true}, {"good tls server name", Policy{From: "https://httpbin.corp.example", To: "https://internal-host-name", TLSServerName: "httpbin.corp.notatld"}, false}, + {"good kube service account token file", Policy{From: "https://httpbin.corp.example", To: "https://internal-host-name", KubernetesServiceAccountTokenFile: "testdata/kubeserviceaccount.token"}, false}, + {"bad kube service account token file", Policy{From: "https://httpbin.corp.example", To: "https://internal-host-name", KubernetesServiceAccountTokenFile: "testdata/missing.token"}, true}, + {"good kube service account token", Policy{From: "https://httpbin.corp.example", To: "https://internal-host-name", KubernetesServiceAccountToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1OTY1MDk4MjIsImV4cCI6MTYyODA0NTgyMiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.H0I6ccQrL6sKobsKQj9dqNcLw_INhU9_xJsVyCkgkiY"}, false}, + {"bad kube service account token and file", Policy{From: "https://httpbin.corp.example", To: "https://internal-host-name", KubernetesServiceAccountToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1OTY1MDk4MjIsImV4cCI6MTYyODA0NTgyMiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.H0I6ccQrL6sKobsKQj9dqNcLw_INhU9_xJsVyCkgkiY", KubernetesServiceAccountTokenFile: "testdata/kubeserviceaccount.token"}, true}, } for _, tt := range tests { diff --git a/config/testdata/kubeserviceaccount.token b/config/testdata/kubeserviceaccount.token new file mode 100644 index 000000000..a86b4165e --- /dev/null +++ b/config/testdata/kubeserviceaccount.token @@ -0,0 +1 @@ +eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1OTY1MDk4MjIsImV4cCI6MTYyODA0NTgyMiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.H0I6ccQrL6sKobsKQj9dqNcLw_INhU9_xJsVyCkgkiY \ No newline at end of file diff --git a/docs/reference/readme.md b/docs/reference/readme.md index 38c055ad0..36700be20 100644 --- a/docs/reference/readme.md +++ b/docs/reference/readme.md @@ -961,6 +961,16 @@ Requires setting [Google Cloud Serverless Authentication Service Account](./#goo `From` is externally accessible source of the proxied request. +### Kubernetes Service Account Token +- `yaml`/`json` setting: `kubernetes_service_account_token` / `kubernetes_service_account_token_file` +- Type: `string` or relative file location containing a Kubernetes bearer token +- Optional +- Example: `eyJ0eXAiOiJKV1QiLCJhbGciOiJ...` or `/var/run/secrets/kubernetes.io/serviceaccount/token` + +Use this token to authenticate requests to a Kubernetes API server. + +Pomerium will [https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation](impersonate) the Pomerium user's identity, and Kubernetes RBAC can be applied to IdP user and groups. + ### Path - `yaml`/`json` setting: `path`