mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 02:02:43 +00:00 
			
		
		
		
	Backport of Fix consul token revocation with namespace specific policies into release/1.15.x (#23776)
* backport of commit 72d66e2813
---------
Co-authored-by: davidadeleon <56207066+davidadeleon@users.noreply.github.com>
Co-authored-by: robmonte <17119716+robmonte@users.noreply.github.com>
			
			
This commit is contained in:
		 hc-github-team-secure-vault-core
					hc-github-team-secure-vault-core
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							64f9d236be
						
					
				
				
					commit
					71a61a7030
				
			| @@ -849,6 +849,22 @@ func TestBackend_Roles(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestBackend_Enterprise_Diff_Namespace_Revocation(t *testing.T) { | ||||
| 	if _, hasLicense := os.LookupEnv("CONSUL_LICENSE"); !hasLicense { | ||||
| 		t.Skip("Skipping: No enterprise license found") | ||||
| 	} | ||||
|  | ||||
| 	testBackendEntDiffNamespaceRevocation(t) | ||||
| } | ||||
|  | ||||
| func TestBackend_Enterprise_Diff_Partition_Revocation(t *testing.T) { | ||||
| 	if _, hasLicense := os.LookupEnv("CONSUL_LICENSE"); !hasLicense { | ||||
| 		t.Skip("Skipping: No enterprise license found") | ||||
| 	} | ||||
|  | ||||
| 	testBackendEntDiffPartitionRevocation(t) | ||||
| } | ||||
|  | ||||
| func TestBackend_Enterprise_Namespace(t *testing.T) { | ||||
| 	if _, hasLicense := os.LookupEnv("CONSUL_LICENSE"); !hasLicense { | ||||
| 		t.Skip("Skipping: No enterprise license found") | ||||
| @@ -865,6 +881,268 @@ func TestBackend_Enterprise_Partition(t *testing.T) { | ||||
| 	testBackendEntPartition(t) | ||||
| } | ||||
|  | ||||
| func testBackendEntDiffNamespaceRevocation(t *testing.T) { | ||||
| 	config := logical.TestBackendConfig() | ||||
| 	config.StorageView = &logical.InmemStorage{} | ||||
| 	b, err := Factory(context.Background(), config) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	cleanup, consulConfig := consul.PrepareTestContainer(t, "", true, true) | ||||
| 	defer cleanup() | ||||
|  | ||||
| 	// Perform additional Consul configuration | ||||
| 	consulapiConfig := consulapi.DefaultNonPooledConfig() | ||||
| 	consulapiConfig.Address = consulConfig.Address() | ||||
| 	consulapiConfig.Token = consulConfig.Token | ||||
| 	client, err := consulapi.NewClient(consulapiConfig) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Create Policy in default namespace to manage ACLs in a different | ||||
| 	// namespace | ||||
| 	nsPol := &consulapi.ACLPolicy{ | ||||
| 		Name:        "diff-ns-test", | ||||
| 		Description: "policy to test management of ACLs in one ns from another", | ||||
| 		Rules: `namespace "ns1" { | ||||
| 			acl="write" | ||||
| 		} | ||||
| 		`, | ||||
| 	} | ||||
| 	pol, _, err := client.ACL().PolicyCreate(nsPol, nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Create new Token in default namespace with new ACL | ||||
| 	cToken, _, err := client.ACL().TokenCreate( | ||||
| 		&consulapi.ACLToken{ | ||||
| 			Policies: []*consulapi.ACLLink{{ID: pol.ID}}, | ||||
| 		}, nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Write backend config | ||||
| 	connData := map[string]interface{}{ | ||||
| 		"address": consulConfig.Address(), | ||||
| 		"token":   cToken.SecretID, | ||||
| 	} | ||||
|  | ||||
| 	req := &logical.Request{ | ||||
| 		Storage:   config.StorageView, | ||||
| 		Operation: logical.UpdateOperation, | ||||
| 		Path:      "config/access", | ||||
| 		Data:      connData, | ||||
| 	} | ||||
| 	_, err = b.HandleRequest(context.Background(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Create the role in namespace "ns1" | ||||
| 	req.Path = "roles/test-ns" | ||||
| 	req.Data = map[string]interface{}{ | ||||
| 		"consul_policies":  []string{"ns-test"}, | ||||
| 		"lease":            "6h", | ||||
| 		"consul_namespace": "ns1", | ||||
| 	} | ||||
| 	_, err = b.HandleRequest(context.Background(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Get Token | ||||
| 	req.Operation = logical.ReadOperation | ||||
| 	req.Path = "creds/test-ns" | ||||
| 	resp, err := b.HandleRequest(context.Background(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if resp == nil { | ||||
| 		t.Fatal("resp nil") | ||||
| 	} | ||||
| 	if resp.IsError() { | ||||
| 		t.Fatalf("resp is error: %v", resp.Error()) | ||||
| 	} | ||||
|  | ||||
| 	generatedSecret := resp.Secret | ||||
| 	generatedSecret.TTL = 6 * time.Hour | ||||
|  | ||||
| 	// Verify Secret | ||||
| 	var d struct { | ||||
| 		Token           string `mapstructure:"token"` | ||||
| 		Accessor        string `mapstructure:"accessor"` | ||||
| 		ConsulNamespace string `mapstructure:"consul_namespace"` | ||||
| 	} | ||||
| 	if err := mapstructure.Decode(resp.Data, &d); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if d.ConsulNamespace != "ns1" { | ||||
| 		t.Fatalf("Failed to access namespace") | ||||
| 	} | ||||
|  | ||||
| 	// Revoke the credential | ||||
| 	req.Operation = logical.RevokeOperation | ||||
| 	req.Secret = generatedSecret | ||||
| 	_, err = b.HandleRequest(context.Background(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Revocation failed: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// Build a management client and verify that the token does not exist anymore | ||||
| 	consulmgmtConfig := consulapi.DefaultNonPooledConfig() | ||||
| 	consulmgmtConfig.Address = connData["address"].(string) | ||||
| 	consulmgmtConfig.Token = connData["token"].(string) | ||||
| 	mgmtclient, err := consulapi.NewClient(consulmgmtConfig) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	q := &consulapi.QueryOptions{ | ||||
| 		Datacenter: "DC1", | ||||
| 		Namespace:  "ns1", | ||||
| 	} | ||||
|  | ||||
| 	_, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q) | ||||
| 	if err == nil { | ||||
| 		t.Fatal("err: expected error") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func testBackendEntDiffPartitionRevocation(t *testing.T) { | ||||
| 	config := logical.TestBackendConfig() | ||||
| 	config.StorageView = &logical.InmemStorage{} | ||||
| 	b, err := Factory(context.Background(), config) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	cleanup, consulConfig := consul.PrepareTestContainer(t, "", true, true) | ||||
| 	defer cleanup() | ||||
|  | ||||
| 	// Perform additional Consul configuration | ||||
| 	consulapiConfig := consulapi.DefaultNonPooledConfig() | ||||
| 	consulapiConfig.Address = consulConfig.Address() | ||||
| 	consulapiConfig.Token = consulConfig.Token | ||||
| 	client, err := consulapi.NewClient(consulapiConfig) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Create Policy in default partition to manage ACLs in a different | ||||
| 	// partition | ||||
| 	partPol := &consulapi.ACLPolicy{ | ||||
| 		Name:        "diff-part-test", | ||||
| 		Description: "policy to test management of ACLs in one part from another", | ||||
| 		Rules: `partition "part1" { | ||||
| 			acl="write" | ||||
| 		} | ||||
| 		`, | ||||
| 	} | ||||
| 	pol, _, err := client.ACL().PolicyCreate(partPol, nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Create new Token in default partition with new ACL | ||||
| 	cToken, _, err := client.ACL().TokenCreate( | ||||
| 		&consulapi.ACLToken{ | ||||
| 			Policies: []*consulapi.ACLLink{{ID: pol.ID}}, | ||||
| 		}, nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Write backend config | ||||
| 	connData := map[string]interface{}{ | ||||
| 		"address": consulConfig.Address(), | ||||
| 		"token":   cToken.SecretID, | ||||
| 	} | ||||
|  | ||||
| 	req := &logical.Request{ | ||||
| 		Storage:   config.StorageView, | ||||
| 		Operation: logical.UpdateOperation, | ||||
| 		Path:      "config/access", | ||||
| 		Data:      connData, | ||||
| 	} | ||||
| 	_, err = b.HandleRequest(context.Background(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Create the role in partition "part1" | ||||
| 	req.Path = "roles/test-part" | ||||
| 	req.Data = map[string]interface{}{ | ||||
| 		"consul_policies": []string{"part-test"}, | ||||
| 		"lease":           "6h", | ||||
| 		"partition":       "part1", | ||||
| 	} | ||||
| 	_, err = b.HandleRequest(context.Background(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Get Token | ||||
| 	req.Operation = logical.ReadOperation | ||||
| 	req.Path = "creds/test-part" | ||||
| 	resp, err := b.HandleRequest(context.Background(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if resp == nil { | ||||
| 		t.Fatal("resp nil") | ||||
| 	} | ||||
| 	if resp.IsError() { | ||||
| 		t.Fatalf("resp is error: %v", resp.Error()) | ||||
| 	} | ||||
|  | ||||
| 	generatedSecret := resp.Secret | ||||
| 	generatedSecret.TTL = 6 * time.Hour | ||||
|  | ||||
| 	// Verify Secret | ||||
| 	var d struct { | ||||
| 		Token     string `mapstructure:"token"` | ||||
| 		Accessor  string `mapstructure:"accessor"` | ||||
| 		Partition string `mapstructure:"partition"` | ||||
| 	} | ||||
| 	if err := mapstructure.Decode(resp.Data, &d); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if d.Partition != "part1" { | ||||
| 		t.Fatalf("Failed to access partition") | ||||
| 	} | ||||
|  | ||||
| 	// Revoke the credential | ||||
| 	req.Operation = logical.RevokeOperation | ||||
| 	req.Secret = generatedSecret | ||||
| 	_, err = b.HandleRequest(context.Background(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Revocation failed: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// Build a management client and verify that the token does not exist anymore | ||||
| 	consulmgmtConfig := consulapi.DefaultNonPooledConfig() | ||||
| 	consulmgmtConfig.Address = connData["address"].(string) | ||||
| 	consulmgmtConfig.Token = connData["token"].(string) | ||||
| 	mgmtclient, err := consulapi.NewClient(consulmgmtConfig) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	q := &consulapi.QueryOptions{ | ||||
| 		Datacenter: "DC1", | ||||
| 		Partition:  "part1", | ||||
| 	} | ||||
|  | ||||
| 	_, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q) | ||||
| 	if err == nil { | ||||
| 		t.Fatal("err: expected error") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func testBackendEntNamespace(t *testing.T) { | ||||
| 	config := logical.TestBackendConfig() | ||||
| 	config.StorageView = &logical.InmemStorage{} | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/hashicorp/consul/api" | ||||
| 	"github.com/hashicorp/vault/sdk/framework" | ||||
| 	"github.com/hashicorp/vault/sdk/logical" | ||||
| ) | ||||
| @@ -84,6 +85,24 @@ func (b *backend) secretTokenRevoke(ctx context.Context, req *logical.Request, d | ||||
| 		version = versionRaw.(string) | ||||
| 	} | ||||
|  | ||||
| 	// Extract Consul Namespace and Partition info from secret | ||||
| 	var revokeWriteOptions *api.WriteOptions | ||||
| 	var namespace, partition string | ||||
|  | ||||
| 	namespaceRaw, ok := req.Data["consul_namespace"] | ||||
| 	if ok { | ||||
| 		namespace = namespaceRaw.(string) | ||||
| 	} | ||||
| 	partitionRaw, ok := req.Data["partition"] | ||||
| 	if ok { | ||||
| 		partition = partitionRaw.(string) | ||||
| 	} | ||||
|  | ||||
| 	revokeWriteOptions = &api.WriteOptions{ | ||||
| 		Namespace: namespace, | ||||
| 		Partition: partition, | ||||
| 	} | ||||
|  | ||||
| 	switch version { | ||||
| 	case "": | ||||
| 		// Pre 1.4 tokens | ||||
| @@ -92,7 +111,7 @@ func (b *backend) secretTokenRevoke(ctx context.Context, req *logical.Request, d | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	case tokenPolicyType: | ||||
| 		_, err := c.ACL().TokenDelete(tokenRaw.(string), nil) | ||||
| 		_, err := c.ACL().TokenDelete(tokenRaw.(string), revokeWriteOptions) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|   | ||||
							
								
								
									
										3
									
								
								changelog/23010.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/23010.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| ```release-note:bug | ||||
| secrets/consul: Fix revocations when Vault has an access token using specific namespace and admin partition policies | ||||
| ``` | ||||
		Reference in New Issue
	
	Block a user