github.com/hernad/nomad@v1.6.112/e2e/acl/acl_token_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package acl
     5  
     6  import (
     7  	"fmt"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/hernad/nomad/api"
    12  	"github.com/hernad/nomad/e2e/e2eutil"
    13  	"github.com/hernad/nomad/helper/uuid"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  var (
    18  	minTokenExpiryDur = 1 * time.Second
    19  	maxTokenExpiryDur = 24 * time.Hour
    20  )
    21  
    22  // testACLTokenExpiration tests tokens that have expiration values set.
    23  // Expirations are timing based which makes this test sensitive to timing
    24  // problems when running the E2E suite.
    25  //
    26  // When running the test, the Nomad server ACL config must have the
    27  // token_min_expiration_ttl value set to minTokenExpiryDur. The
    28  // token_min_expiration_ttl value must be set to maxTokenExpiryDur if this
    29  // value differs from the default. This is so we can test expired tokens,
    30  // without blocking for an extended period of time as well as other time
    31  // related aspects.
    32  func testACLTokenExpiration(t *testing.T) {
    33  
    34  	nomadClient := e2eutil.NomadClient(t)
    35  
    36  	// Create and defer the Cleanup process. This is used to remove all
    37  	// resources created by this test and covers situations where the test
    38  	// fails or during normal running.
    39  	cleanUpProcess := NewCleanup()
    40  	defer cleanUpProcess.Run(t, nomadClient)
    41  
    42  	// Create an ACL policy which will be assigned to the created ACL tokens.
    43  	customNamespacePolicy := api.ACLPolicy{
    44  		Name:        "e2e-acl-" + uuid.Short(),
    45  		Description: "E2E ACL Role Testing",
    46  		Rules:       `namespace "default" {policy = "read"}`,
    47  	}
    48  	_, err := nomadClient.ACLPolicies().Upsert(&customNamespacePolicy, nil)
    49  	require.NoError(t, err)
    50  
    51  	cleanUpProcess.Add(customNamespacePolicy.Name, ACLPolicyTestResourceType)
    52  
    53  	// Create our default query options which can be used when testing a token
    54  	// against the API. The caller should update the auth token as needed.
    55  	defaultNSQueryMeta := api.QueryOptions{Namespace: "default"}
    56  
    57  	// Attempt to create a token with a lower than acceptable TTL and ensure we
    58  	// get an error.
    59  	tokenTTLLow := api.ACLToken{
    60  		Name:          "e2e-acl-" + uuid.Short(),
    61  		Type:          "client",
    62  		Policies:      []string{customNamespacePolicy.Name},
    63  		ExpirationTTL: minTokenExpiryDur / 2,
    64  	}
    65  	aclTokenCreateResp, _, err := nomadClient.ACLTokens().Create(&tokenTTLLow, nil)
    66  	require.ErrorContains(t, err, fmt.Sprintf(
    67  		"expiration time cannot be less than %s in the future", minTokenExpiryDur))
    68  	require.Nil(t, aclTokenCreateResp)
    69  
    70  	// Attempt to create a token with a higher than acceptable TTL and ensure
    71  	// we get an error.
    72  	tokenTTLHigh := api.ACLToken{
    73  		Name:          "e2e-acl-" + uuid.Short(),
    74  		Type:          "client",
    75  		Policies:      []string{customNamespacePolicy.Name},
    76  		ExpirationTTL: 8766 * time.Hour,
    77  	}
    78  	aclTokenCreateResp, _, err = nomadClient.ACLTokens().Create(&tokenTTLHigh, nil)
    79  	require.ErrorContains(t, err, fmt.Sprintf(
    80  		"expiration time cannot be more than %s in the future", maxTokenExpiryDur))
    81  	require.Nil(t, aclTokenCreateResp)
    82  
    83  	// Create an ACL token that has a fairly standard TTL that will allow us to
    84  	// make successful calls without it expiring.
    85  	tokenNormalExpiry := api.ACLToken{
    86  		Name:          "e2e-acl-" + uuid.Short(),
    87  		Type:          "client",
    88  		Policies:      []string{customNamespacePolicy.Name},
    89  		ExpirationTTL: 10 * time.Minute,
    90  	}
    91  	tokenNormalExpiryCreateResp, _, err := nomadClient.ACLTokens().Create(&tokenNormalExpiry, nil)
    92  	require.NoError(t, err)
    93  	require.NotNil(t, tokenNormalExpiryCreateResp)
    94  	require.Equal(t,
    95  		*tokenNormalExpiryCreateResp.ExpirationTime,
    96  		tokenNormalExpiryCreateResp.CreateTime.Add(tokenNormalExpiryCreateResp.ExpirationTTL))
    97  
    98  	cleanUpProcess.Add(tokenNormalExpiryCreateResp.AccessorID, ACLTokenTestResourceType)
    99  
   100  	// Add the token to our query options and ensure we can now list jobs with
   101  	// the default namespace.
   102  	defaultNSQueryMeta.AuthToken = tokenNormalExpiryCreateResp.SecretID
   103  
   104  	jobListResp, _, err := nomadClient.Jobs().List(&defaultNSQueryMeta)
   105  	require.NoError(t, err)
   106  
   107  	// Create an ACL token with the lowest expiry TTL possible, so it will
   108  	// expire almost immediately.
   109  	tokenQuickExpiry := api.ACLToken{
   110  		Name:          "e2e-acl-" + uuid.Short(),
   111  		Type:          "client",
   112  		Policies:      []string{customNamespacePolicy.Name},
   113  		ExpirationTTL: minTokenExpiryDur,
   114  	}
   115  	tokenQuickExpiryCreateResp, _, err := nomadClient.ACLTokens().Create(&tokenQuickExpiry, nil)
   116  	require.NoError(t, err)
   117  	require.NotNil(t, tokenQuickExpiryCreateResp)
   118  	require.Equal(t,
   119  		*tokenQuickExpiryCreateResp.ExpirationTime,
   120  		tokenQuickExpiryCreateResp.CreateTime.Add(tokenQuickExpiryCreateResp.ExpirationTTL))
   121  
   122  	cleanUpProcess.Add(tokenQuickExpiryCreateResp.AccessorID, ACLTokenTestResourceType)
   123  
   124  	// Block the test (sorry) until the token has expired.
   125  	time.Sleep(tokenQuickExpiry.ExpirationTTL)
   126  
   127  	// Update our query options and attempt to list the job using our expired
   128  	// token.
   129  	defaultNSQueryMeta.AuthToken = tokenQuickExpiryCreateResp.SecretID
   130  
   131  	jobListResp, _, err = nomadClient.Jobs().List(&defaultNSQueryMeta)
   132  	require.ErrorContains(t, err, "ACL token expired")
   133  	require.Nil(t, jobListResp)
   134  
   135  	cleanUpProcess.Remove(tokenQuickExpiryCreateResp.AccessorID, ACLTokenTestResourceType)
   136  
   137  	// List the tokens to ensure the output correctly shows the token
   138  	// expiration. Other tests may have left tokens in state, so do not perform
   139  	// a length check.
   140  	tokenListResp, _, err := nomadClient.ACLTokens().List(nil)
   141  	require.NoError(t, err)
   142  
   143  	var quickExpiryFound, normalExpiryFound bool
   144  
   145  	for _, token := range tokenListResp {
   146  		switch token.AccessorID {
   147  		case tokenQuickExpiryCreateResp.AccessorID:
   148  			quickExpiryFound = true
   149  			require.NotNil(t, token.ExpirationTime)
   150  		case tokenNormalExpiryCreateResp.AccessorID:
   151  			normalExpiryFound = true
   152  			require.NotNil(t, token.ExpirationTime)
   153  		default:
   154  			continue
   155  		}
   156  	}
   157  
   158  	require.True(t, quickExpiryFound)
   159  	require.True(t, normalExpiryFound)
   160  
   161  	// Ensure we can manually delete unexpired tokens and that they are
   162  	// immediately removed from state.
   163  	_, err = nomadClient.ACLTokens().Delete(tokenNormalExpiryCreateResp.AccessorID, nil)
   164  	require.NoError(t, err)
   165  
   166  	tokenNormalExpiryReadResp, _, err := nomadClient.ACLTokens().Info(tokenNormalExpiryCreateResp.AccessorID, nil)
   167  	require.ErrorContains(t, err, "ACL token not found")
   168  	require.Nil(t, tokenNormalExpiryReadResp)
   169  
   170  	cleanUpProcess.Remove(tokenNormalExpiryCreateResp.AccessorID, ACLTokenTestResourceType)
   171  }
   172  
   173  // testACLTokenRolePolicyAssignment tests that tokens allow and have the
   174  // expected permissions when created or updated with a combination of role and
   175  // policy assignments.
   176  func testACLTokenRolePolicyAssignment(t *testing.T) {
   177  
   178  	nomadClient := e2eutil.NomadClient(t)
   179  
   180  	// Create and defer the Cleanup process. This is used to remove all
   181  	// resources created by this test and covers situations where the test
   182  	// fails or during normal running.
   183  	cleanUpProcess := NewCleanup()
   184  	defer cleanUpProcess.Run(t, nomadClient)
   185  
   186  	// Create two ACL policies which will be used throughout this test. One
   187  	// grants read access to the default namespace, the other grants read
   188  	// access to node objects.
   189  	defaultNamespacePolicy := api.ACLPolicy{
   190  		Name:        "e2e-acl-" + uuid.Short(),
   191  		Description: "E2E ACL Role Testing",
   192  		Rules:       `namespace "default" {policy = "read"}`,
   193  	}
   194  	_, err := nomadClient.ACLPolicies().Upsert(&defaultNamespacePolicy, nil)
   195  	require.NoError(t, err)
   196  
   197  	cleanUpProcess.Add(defaultNamespacePolicy.Name, ACLPolicyTestResourceType)
   198  
   199  	nodePolicy := api.ACLPolicy{
   200  		Name:        "e2e-acl-" + uuid.Short(),
   201  		Description: "E2E ACL Role Testing",
   202  		Rules:       `node { policy = "read" }`,
   203  	}
   204  	_, err = nomadClient.ACLPolicies().Upsert(&nodePolicy, nil)
   205  	require.NoError(t, err)
   206  
   207  	cleanUpProcess.Add(nodePolicy.Name, ACLPolicyTestResourceType)
   208  
   209  	// Create an ACL role that has the node read policy assigned.
   210  	aclRole := api.ACLRole{
   211  		Name:        "e2e-acl-" + uuid.Short(),
   212  		Description: "E2E ACL Role Testing",
   213  		Policies:    []*api.ACLRolePolicyLink{{Name: nodePolicy.Name}},
   214  	}
   215  	aclRoleCreateResp, _, err := nomadClient.ACLRoles().Create(&aclRole, nil)
   216  	require.NoError(t, err)
   217  	require.NotNil(t, aclRoleCreateResp)
   218  	require.NotEmpty(t, aclRoleCreateResp.ID)
   219  
   220  	cleanUpProcess.Add(aclRoleCreateResp.ID, ACLRoleTestResourceType)
   221  
   222  	// Create an ACL token which only has the ACL policy which allows reading
   223  	// the default namespace assigned.
   224  	token := api.ACLToken{
   225  		Name:     "e2e-acl-" + uuid.Short(),
   226  		Type:     "client",
   227  		Policies: []string{defaultNamespacePolicy.Name},
   228  	}
   229  	aclTokenCreateResp, _, err := nomadClient.ACLTokens().Create(&token, nil)
   230  	require.NoError(t, err)
   231  	require.NotNil(t, aclTokenCreateResp)
   232  	require.NotEmpty(t, aclTokenCreateResp.SecretID)
   233  
   234  	cleanUpProcess.Add(aclTokenCreateResp.AccessorID, ACLTokenTestResourceType)
   235  
   236  	// Test that the token can read the default namespace, but that it cannot
   237  	// read node objects.
   238  	defaultNSQueryMeta := api.QueryOptions{Namespace: "default", AuthToken: aclTokenCreateResp.SecretID}
   239  	jobListResp, _, err := nomadClient.Jobs().List(&defaultNSQueryMeta)
   240  	require.NoError(t, err)
   241  
   242  	nodeStubList, _, err := nomadClient.Nodes().List(&defaultNSQueryMeta)
   243  	require.ErrorContains(t, err, "Permission denied")
   244  	require.Nil(t, nodeStubList)
   245  
   246  	// Update the token to also include the ACL role which will allow reading
   247  	// node objects.
   248  	newToken := aclTokenCreateResp
   249  	newToken.Roles = []*api.ACLTokenRoleLink{{ID: aclRoleCreateResp.ID}}
   250  	aclTokenUpdateResp, _, err := nomadClient.ACLTokens().Update(newToken, nil)
   251  	require.NoError(t, err)
   252  	require.Equal(t, aclTokenUpdateResp.SecretID, aclTokenCreateResp.SecretID)
   253  
   254  	// Test that the token can now read the default namespace and node objects.
   255  	jobListResp, _, err = nomadClient.Jobs().List(&defaultNSQueryMeta)
   256  	require.NoError(t, err)
   257  
   258  	nodeStubList, _, err = nomadClient.Nodes().List(&defaultNSQueryMeta)
   259  	require.NoError(t, err)
   260  	require.Greater(t, len(nodeStubList), 0)
   261  
   262  	// Remove the policy assignment from the token.
   263  	newToken.Policies = []string{}
   264  	aclTokenUpdateResp, _, err = nomadClient.ACLTokens().Update(newToken, nil)
   265  	require.NoError(t, err)
   266  	require.Equal(t, aclTokenUpdateResp.SecretID, aclTokenCreateResp.SecretID)
   267  
   268  	// Test that the token can now only read node objects and not the default
   269  	// namespace.
   270  	jobListResp, _, err = nomadClient.Jobs().List(&defaultNSQueryMeta)
   271  	require.ErrorContains(t, err, "Permission denied")
   272  	require.Nil(t, jobListResp)
   273  
   274  	nodeStubList, _, err = nomadClient.Nodes().List(&defaultNSQueryMeta)
   275  	require.NoError(t, err)
   276  	require.Greater(t, len(nodeStubList), 0)
   277  
   278  	// Try and remove the role assignment which should result in a validation
   279  	// error as it needs to include either a policy or role linking.
   280  	newToken.Roles = nil
   281  	aclTokenUpdateResp, _, err = nomadClient.ACLTokens().Update(newToken, nil)
   282  	require.ErrorContains(t, err, "client token missing policies or roles")
   283  	require.Nil(t, aclTokenUpdateResp)
   284  
   285  	// Create a new token that has both the role and policy linking in place.
   286  	token = api.ACLToken{
   287  		Name:     "e2e-acl-" + uuid.Short(),
   288  		Type:     "client",
   289  		Policies: []string{defaultNamespacePolicy.Name},
   290  		Roles:    []*api.ACLTokenRoleLink{{ID: aclRoleCreateResp.ID}},
   291  	}
   292  	aclTokenCreateResp, _, err = nomadClient.ACLTokens().Create(&token, nil)
   293  	require.NoError(t, err)
   294  	require.NotNil(t, aclTokenCreateResp)
   295  	require.NotEmpty(t, aclTokenCreateResp.SecretID)
   296  
   297  	cleanUpProcess.Add(aclTokenCreateResp.AccessorID, ACLTokenTestResourceType)
   298  
   299  	// Test that the token is working as expected.
   300  	defaultNSQueryMeta.AuthToken = aclTokenCreateResp.SecretID
   301  
   302  	jobListResp, _, err = nomadClient.Jobs().List(&defaultNSQueryMeta)
   303  	require.NoError(t, err)
   304  
   305  	nodeStubList, _, err = nomadClient.Nodes().List(&defaultNSQueryMeta)
   306  	require.NoError(t, err)
   307  	require.Greater(t, len(nodeStubList), 0)
   308  
   309  	// Now delete both the policy and the role from underneath the token. This
   310  	// differs to the graceful approaches above where the token was modified to
   311  	// remove the assignment.
   312  	_, err = nomadClient.ACLPolicies().Delete(defaultNamespacePolicy.Name, nil)
   313  	require.NoError(t, err)
   314  	cleanUpProcess.Remove(defaultNamespacePolicy.Name, ACLPolicyTestResourceType)
   315  
   316  	_, err = nomadClient.ACLRoles().Delete(aclRoleCreateResp.ID, nil)
   317  	require.NoError(t, err)
   318  	cleanUpProcess.Remove(aclRoleCreateResp.ID, ACLRoleTestResourceType)
   319  
   320  	// The token now should not have any power here; quite different to
   321  	// Gandalf's power over the spell on King Theoden.
   322  	jobListResp, _, err = nomadClient.Jobs().List(&defaultNSQueryMeta)
   323  	require.ErrorContains(t, err, "Permission denied")
   324  	require.Nil(t, jobListResp)
   325  
   326  	nodeStubList, _, err = nomadClient.Nodes().List(&defaultNSQueryMeta)
   327  	require.ErrorContains(t, err, "Permission denied")
   328  	require.Nil(t, nodeStubList)
   329  }