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 }