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 }