github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/e2e/acl/acl_token_test.go (about)

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