// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package acl import ( "fmt" "strings" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/command/helpers" "github.com/hashicorp/hcl" "github.com/mitchellh/mapstructure" ) func GetTokenAccessorIDFromPartial(client *api.Client, partialAccessorID string) (string, error) { if partialAccessorID == "anonymous" { return acl.AnonymousTokenID, nil } // the full UUID string was given if len(partialAccessorID) == 36 { return partialAccessorID, nil } tokens, _, err := client.ACL().TokenList(nil) if err != nil { return "", err } tokenAccessorID := "" for _, token := range tokens { if strings.HasPrefix(token.AccessorID, partialAccessorID) { if tokenAccessorID != "" { return "", fmt.Errorf("Partial token ID is not unique") } tokenAccessorID = token.AccessorID } } if tokenAccessorID == "" { return "", fmt.Errorf("No such token ID with prefix: %s", partialAccessorID) } return tokenAccessorID, nil } func GetPolicyIDFromPartial(client *api.Client, partialID string) (string, error) { // try the builtin policies (by name) first for _, policy := range structs.ACLBuiltinPolicies { if partialID == policy.Name { return policy.ID, nil } } if policy, ok := structs.ACLBuiltinPolicies[partialID]; ok { return policy.ID, nil } // The full UUID string was given if len(partialID) == 36 { return partialID, nil } policies, _, err := client.ACL().PolicyList(nil) if err != nil { return "", err } policyID := "" for _, policy := range policies { if strings.HasPrefix(policy.ID, partialID) { if policyID != "" { return "", fmt.Errorf("Partial policy ID is not unique") } policyID = policy.ID } } if policyID == "" { return "", fmt.Errorf("No such policy ID with prefix: %s", partialID) } return policyID, nil } func GetPolicyByName(client *api.Client, name string) (*api.ACLPolicy, error) { if name == "" { return nil, fmt.Errorf("No name specified") } policy, _, err := client.ACL().PolicyReadByName(name, nil) if err != nil { return nil, fmt.Errorf("Failed to find policy with name %s: %w", name, err) } return policy, nil } func GetPolicyIDByName(client *api.Client, name string) (string, error) { policy, err := GetPolicyByName(client, name) if err != nil { return "", err } if policy == nil { return "", fmt.Errorf("No such policy with name: %s", name) } return policy.ID, nil } func GetRoleIDFromPartial(client *api.Client, partialID string) (string, error) { // the full UUID string was given if len(partialID) == 36 { return partialID, nil } roles, _, err := client.ACL().RoleList(nil) if err != nil { return "", err } roleID := "" for _, role := range roles { if strings.HasPrefix(role.ID, partialID) { if roleID != "" { return "", fmt.Errorf("Partial role ID is not unique") } roleID = role.ID } } if roleID == "" { return "", fmt.Errorf("No such role ID with prefix: %s", partialID) } return roleID, nil } func GetRoleIDByName(client *api.Client, name string) (string, error) { if name == "" { return "", fmt.Errorf("No name specified") } roles, _, err := client.ACL().RoleList(nil) if err != nil { return "", err } for _, role := range roles { if role.Name == name { return role.ID, nil } } return "", fmt.Errorf("No such role with name %s", name) } func GetBindingRuleIDFromPartial(client *api.Client, partialID string) (string, error) { // the full UUID string was given if len(partialID) == 36 { return partialID, nil } rules, _, err := client.ACL().BindingRuleList("", nil) if err != nil { return "", err } ruleID := "" for _, rule := range rules { if strings.HasPrefix(rule.ID, partialID) { if ruleID != "" { return "", fmt.Errorf("Partial rule ID is not unique") } ruleID = rule.ID } } if ruleID == "" { return "", fmt.Errorf("no such rule ID with prefix: %s: %w", partialID, acl.ErrNotFound) } return ruleID, nil } func ExtractServiceIdentities(serviceIdents []string) ([]*api.ACLServiceIdentity, error) { var out []*api.ACLServiceIdentity for _, svcidRaw := range serviceIdents { parts := strings.Split(svcidRaw, ":") switch len(parts) { case 2: out = append(out, &api.ACLServiceIdentity{ ServiceName: parts[0], Datacenters: strings.Split(parts[1], ","), }) case 1: out = append(out, &api.ACLServiceIdentity{ ServiceName: parts[0], }) default: return nil, fmt.Errorf("Malformed -service-identity argument: %q", svcidRaw) } } return out, nil } func ExtractNodeIdentities(nodeIdents []string) ([]*api.ACLNodeIdentity, error) { var out []*api.ACLNodeIdentity for _, nodeidRaw := range nodeIdents { parts := strings.Split(nodeidRaw, ":") switch len(parts) { case 2: out = append(out, &api.ACLNodeIdentity{ NodeName: parts[0], Datacenter: parts[1], }) default: return nil, fmt.Errorf("Malformed -node-identity argument: %q", nodeidRaw) } } return out, nil } func ExtractTemplatedPolicies(templatedPolicy string, templatedPolicyFile string, templatedPolicyVariables []string) ([]*api.ACLTemplatedPolicy, error) { var out []*api.ACLTemplatedPolicy if templatedPolicy == "" && templatedPolicyFile == "" { return out, nil } if templatedPolicy != "" { parsedVariables, err := getTemplatedPolicyVariables(templatedPolicyVariables) if err != nil { return nil, err } out = append(out, &api.ACLTemplatedPolicy{ TemplateName: templatedPolicy, TemplateVariables: parsedVariables, }) } if templatedPolicyFile != "" { fileData, err := helpers.LoadFromFile(templatedPolicyFile) if err != nil { return nil, err } var config map[string]map[string][]api.ACLTemplatedPolicyVariables err = hcl.Decode(&config, fileData) if err != nil { return nil, err } for templateName, templateVariables := range config["TemplatedPolicy"] { for _, tp := range templateVariables { out = append(out, &api.ACLTemplatedPolicy{ TemplateName: templateName, TemplateVariables: &api.ACLTemplatedPolicyVariables{ Name: tp.Name, }, }) } } } return out, nil } func ExtractBindVars(bindVars map[string]string) (*api.ACLTemplatedPolicyVariables, error) { if len(bindVars) == 0 { return nil, nil } out := &api.ACLTemplatedPolicyVariables{} err := mapstructure.Decode(bindVars, out) return out, err } func getTemplatedPolicyVariables(variables []string) (*api.ACLTemplatedPolicyVariables, error) { if len(variables) == 0 { return nil, nil } out := &api.ACLTemplatedPolicyVariables{} jsonVariables := make(map[string]string) for _, variable := range variables { parts := strings.Split(variable, ":") if len(parts) != 2 { return nil, fmt.Errorf("malformed -var argument: %q, expecting VariableName:Value", variable) } jsonVariables[parts[0]] = parts[1] } err := mapstructure.Decode(jsonVariables, out) return out, err } // TestKubernetesJWT_A is a valid service account jwt extracted from a minikube setup. // // { // "iss": "kubernetes/serviceaccount", // "kubernetes.io/serviceaccount/namespace": "default", // "kubernetes.io/serviceaccount/secret.name": "admin-token-qlz42", // "kubernetes.io/serviceaccount/service-account.name": "admin", // "kubernetes.io/serviceaccount/service-account.uid": "738bc251-6532-11e9-b67f-48e6c8b8ecb5", // "sub": "system:serviceaccount:default:admin" // } const TestKubernetesJWT_A = "eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImFkbWluLXRva2VuLXFsejQyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiNzM4YmMyNTEtNjUzMi0xMWU5LWI2N2YtNDhlNmM4YjhlY2I1Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6YWRtaW4ifQ.ixMlnWrAG7NVuTTKu8cdcYfM7gweS3jlKaEsIBNGOVEjPE7rtXtgMkAwjQTdYR08_0QBjkgzy5fQC5ZNyglSwONJ-bPaXGvhoH1cTnRi1dz9H_63CfqOCvQP1sbdkMeRxNTGVAyWZT76rXoCUIfHP4LY2I8aab0KN9FTIcgZRF0XPTtT70UwGIrSmRpxW38zjiy2ymWL01cc5VWGhJqVysmWmYk3wNp0h5N57H_MOrz4apQR4pKaamzskzjLxO55gpbmZFC76qWuUdexAR7DT2fpbHLOw90atN_NlLMY-VrXyW3-Ei5EhYaVreMB9PSpKwkrA4jULITohV-sxpa1LA" // TestKubernetesJWT_B is a valid service account jwt extracted from a minikube setup. // // { // "iss": "kubernetes/serviceaccount", // "kubernetes.io/serviceaccount/namespace": "default", // "kubernetes.io/serviceaccount/secret.name": "demo-token-kmb9n", // "kubernetes.io/serviceaccount/service-account.name": "demo", // "kubernetes.io/serviceaccount/service-account.uid": "76091af4-4b56-11e9-ac4b-708b11801cbe", // "sub": "system:serviceaccount:default:demo" // } const TestKubernetesJWT_B = "eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlbW8tdG9rZW4ta21iOW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVtbyIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6Ijc2MDkxYWY0LTRiNTYtMTFlOS1hYzRiLTcwOGIxMTgwMWNiZSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlbW8ifQ.ZiAHjijBAOsKdum0Aix6lgtkLkGo9_Tu87dWQ5Zfwnn3r2FejEWDAnftTft1MqqnMzivZ9Wyyki5ZjQRmTAtnMPJuHC-iivqY4Wh4S6QWCJ1SivBv5tMZR79t5t8mE7R1-OHwst46spru1pps9wt9jsA04d3LpV0eeKYgdPTVaQKklxTm397kIMUugA6yINIBQ3Rh8eQqBgNwEmL4iqyYubzHLVkGkoP9MJikFI05vfRiHtYr-piXz6JFDzXMQj9rW6xtMmrBSn79ChbyvC5nz-Nj2rJPnHsb_0rDUbmXY5PpnMhBpdSH-CbZ4j8jsiib6DtaGJhVZeEQ1GjsFAZwQ"