forked from mystiq/dex
storage/kubernetes: guess namespace from the service account token
The in cluster kubernetes client currently requires using the downward API to determine its namespace. However this value can be determine by inspecting the service account token mounted into the pod. As a fallback, use this to guess the current namespace.
This commit is contained in:
parent
ba9f6c6cd6
commit
8c9c5160b6
2 changed files with 95 additions and 6 deletions
|
@ -322,6 +322,32 @@ func loadKubeConfig(kubeConfigPath string) (cluster k8sapi.Cluster, user k8sapi.
|
|||
return
|
||||
}
|
||||
|
||||
func namespaceFromServiceAccountJWT(s string) (string, error) {
|
||||
// The service account token is just a JWT. Parse it as such.
|
||||
parts := strings.Split(s, ".")
|
||||
if len(parts) < 2 {
|
||||
// It's extremely important we don't log the actual service account token.
|
||||
return "", fmt.Errorf("malformed service account token: expected 3 parts got %d", len(parts))
|
||||
}
|
||||
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("malformed service account token: %v", err)
|
||||
}
|
||||
var data struct {
|
||||
// The claim Kubernetes uses to identify which namespace a service account belongs to.
|
||||
//
|
||||
// See: https://github.com/kubernetes/kubernetes/blob/v1.4.3/pkg/serviceaccount/jwt.go#L42
|
||||
Namespace string `json:"kubernetes.io/serviceaccount/namespace"`
|
||||
}
|
||||
if err := json.Unmarshal(payload, &data); err != nil {
|
||||
return "", fmt.Errorf("malformed service account token: %v", err)
|
||||
}
|
||||
if data.Namespace == "" {
|
||||
return "", errors.New(`jwt claim "kubernetes.io/serviceaccount/namespace" not found`)
|
||||
}
|
||||
return data.Namespace, nil
|
||||
}
|
||||
|
||||
func inClusterConfig() (cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, err error) {
|
||||
host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
|
||||
if len(host) == 0 || len(port) == 0 {
|
||||
|
@ -332,17 +358,20 @@ func inClusterConfig() (cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace
|
|||
Server: "https://" + host + ":" + port,
|
||||
CertificateAuthority: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
|
||||
}
|
||||
|
||||
if namespace = os.Getenv("KUBERNETES_POD_NAMESPACE"); namespace == "" {
|
||||
err = fmt.Errorf("unable to load in-cluster configuration, KUBERNETES_POD_NAMESPACE must be defined")
|
||||
return
|
||||
}
|
||||
|
||||
token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
user = k8sapi.AuthInfo{Token: string(token)}
|
||||
|
||||
if namespace = os.Getenv("KUBERNETES_POD_NAMESPACE"); namespace == "" {
|
||||
namespace, err = namespaceFromServiceAccountJWT(user.Token)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to inspect service account token: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
60
storage/kubernetes/client_test.go
Normal file
60
storage/kubernetes/client_test.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package kubernetes
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNamespaceFromServiceAccountJWT(t *testing.T) {
|
||||
namespace, err := namespaceFromServiceAccountJWT(serviceAccountToken)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantNamespace := "dex-test-namespace"
|
||||
if namespace != wantNamespace {
|
||||
t.Errorf("expected namespace %q got %q", wantNamespace, namespace)
|
||||
}
|
||||
}
|
||||
|
||||
var serviceAccountToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZXgtdGVzdC1uYW1lc3BhY2UiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoiZG90aGVyb2JvdC1zZWNyZXQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZG90aGVyb2JvdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjQyYjJhOTRmLTk4MjAtMTFlNi1iZDc0LTJlZmQzOGYxMjYxYyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZXgtdGVzdC1uYW1lc3BhY2U6ZG90aGVyb2JvdCJ9.KViBpPwCiBwxDvAjYUUXoVvLVwqV011aLlYQpNtX12Bh8M-QAFch-3RWlo_SR00bcdFg_nZo9JKACYlF_jHMEsf__PaYms9r7vEaSg0jPfkqnL2WXZktzQRyLBr0n-bxeUrbwIWsKOAC0DfFB5nM8XoXljRmq8yAx8BAdmQp7MIFb4EOV9nYthhua6pjzYyaFSiDiYTjw7HtXOvoL8oepodJ3-37pUKS8vdBvnvUoqC4M1YAhkO5L36JF6KV_RfmG8GPEdNQfXotHcsR-3jKi1n8S5l7Xd-rhrGOhSGQizH3dORzo9GvBAhYeqbq1O-NLzm2EQUiMQayIUx7o4g3Kw"
|
||||
|
||||
// The following program was used to generate the example token. Since we don't want to
|
||||
// import Kubernetes, just leave it as a comment.
|
||||
|
||||
/*
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||
"k8s.io/kubernetes/pkg/util/uuid"
|
||||
)
|
||||
|
||||
func main() {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
sa := api.ServiceAccount{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Namespace: "dex-test-namespace",
|
||||
Name: "dotherobot",
|
||||
UID: uuid.NewUUID(),
|
||||
},
|
||||
}
|
||||
secret := api.Secret{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Namespace: "dex-test-namespace",
|
||||
Name: "dotherobot-secret",
|
||||
UID: uuid.NewUUID(),
|
||||
},
|
||||
}
|
||||
token, err := serviceaccount.JWTTokenGenerator(key).GenerateToken(sa, secret)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(token)
|
||||
}
|
||||
*/
|
Loading…
Reference in a new issue