github.com/uchennaokeke444/nomad@v0.11.8/nomad/consul_test.go (about) 1 package nomad 2 3 import ( 4 "context" 5 "errors" 6 "sync" 7 "testing" 8 "time" 9 10 "github.com/hashicorp/nomad/command/agent/consul" 11 "github.com/hashicorp/nomad/helper" 12 "github.com/hashicorp/nomad/helper/testlog" 13 "github.com/hashicorp/nomad/helper/uuid" 14 "github.com/hashicorp/nomad/nomad/structs" 15 "github.com/stretchr/testify/require" 16 "golang.org/x/time/rate" 17 ) 18 19 var _ ConsulACLsAPI = (*consulACLsAPI)(nil) 20 var _ ConsulACLsAPI = (*mockConsulACLsAPI)(nil) 21 22 type revokeRequest struct { 23 accessorID string 24 committed bool 25 } 26 27 type mockConsulACLsAPI struct { 28 lock sync.Mutex 29 revokeRequests []revokeRequest 30 stopped bool 31 } 32 33 func (m *mockConsulACLsAPI) CheckSIPolicy(_ context.Context, _, _ string) error { 34 panic("not implemented yet") 35 } 36 37 func (m *mockConsulACLsAPI) CreateToken(_ context.Context, _ ServiceIdentityRequest) (*structs.SIToken, error) { 38 panic("not implemented yet") 39 } 40 41 func (m *mockConsulACLsAPI) ListTokens() ([]string, error) { 42 panic("not implemented yet") 43 } 44 45 func (m *mockConsulACLsAPI) Stop() { 46 m.lock.Lock() 47 defer m.lock.Unlock() 48 m.stopped = true 49 } 50 51 type mockPurgingServer struct { 52 purgedAccessorIDs []string 53 failure error 54 } 55 56 func (mps *mockPurgingServer) purgeFunc(accessors []*structs.SITokenAccessor) error { 57 if mps.failure != nil { 58 return mps.failure 59 } 60 61 for _, accessor := range accessors { 62 mps.purgedAccessorIDs = append(mps.purgedAccessorIDs, accessor.AccessorID) 63 } 64 return nil 65 } 66 67 func (m *mockConsulACLsAPI) RevokeTokens(_ context.Context, accessors []*structs.SITokenAccessor, committed bool) bool { 68 return m.storeForRevocation(accessors, committed) 69 } 70 71 func (m *mockConsulACLsAPI) MarkForRevocation(accessors []*structs.SITokenAccessor) { 72 m.storeForRevocation(accessors, true) 73 } 74 75 func (m *mockConsulACLsAPI) storeForRevocation(accessors []*structs.SITokenAccessor, committed bool) bool { 76 m.lock.Lock() 77 defer m.lock.Unlock() 78 79 for _, accessor := range accessors { 80 m.revokeRequests = append(m.revokeRequests, revokeRequest{ 81 accessorID: accessor.AccessorID, 82 committed: committed, 83 }) 84 } 85 return false 86 } 87 88 func TestConsulACLsAPI_CreateToken(t *testing.T) { 89 t.Parallel() 90 91 try := func(t *testing.T, expErr error) { 92 logger := testlog.HCLogger(t) 93 aclAPI := consul.NewMockACLsAPI(logger) 94 aclAPI.SetError(expErr) 95 96 c := NewConsulACLsAPI(aclAPI, logger, nil) 97 98 ctx := context.Background() 99 sii := ServiceIdentityRequest{ 100 AllocID: uuid.Generate(), 101 ClusterID: uuid.Generate(), 102 TaskName: "my-task1-sidecar-proxy", 103 TaskKind: structs.NewTaskKind(structs.ConnectProxyPrefix, "my-service"), 104 } 105 106 token, err := c.CreateToken(ctx, sii) 107 108 if expErr != nil { 109 require.Equal(t, expErr, err) 110 require.Nil(t, token) 111 } else { 112 require.NoError(t, err) 113 require.Equal(t, "my-task1-sidecar-proxy", token.TaskName) 114 require.True(t, helper.IsUUID(token.AccessorID)) 115 require.True(t, helper.IsUUID(token.SecretID)) 116 } 117 } 118 119 t.Run("create token success", func(t *testing.T) { 120 try(t, nil) 121 }) 122 123 t.Run("create token error", func(t *testing.T) { 124 try(t, errors.New("consul broke")) 125 }) 126 } 127 128 func TestConsulACLsAPI_RevokeTokens(t *testing.T) { 129 t.Parallel() 130 131 setup := func(t *testing.T, exp error) (context.Context, ConsulACLsAPI, *structs.SIToken) { 132 logger := testlog.HCLogger(t) 133 aclAPI := consul.NewMockACLsAPI(logger) 134 135 c := NewConsulACLsAPI(aclAPI, logger, nil) 136 137 ctx := context.Background() 138 generated, err := c.CreateToken(ctx, ServiceIdentityRequest{ 139 ClusterID: uuid.Generate(), 140 AllocID: uuid.Generate(), 141 TaskName: "task1-sidecar-proxy", 142 TaskKind: structs.NewTaskKind(structs.ConnectProxyPrefix, "service1"), 143 }) 144 require.NoError(t, err) 145 146 // set the mock error after calling CreateToken for setting up 147 aclAPI.SetError(exp) 148 149 return context.Background(), c, generated 150 } 151 152 accessors := func(ids ...string) (result []*structs.SITokenAccessor) { 153 for _, id := range ids { 154 result = append(result, &structs.SITokenAccessor{AccessorID: id}) 155 } 156 return 157 } 158 159 t.Run("revoke token success", func(t *testing.T) { 160 ctx, c, token := setup(t, nil) 161 retryLater := c.RevokeTokens(ctx, accessors(token.AccessorID), false) 162 require.False(t, retryLater) 163 }) 164 165 t.Run("revoke token non-existent", func(t *testing.T) { 166 ctx, c, _ := setup(t, nil) 167 retryLater := c.RevokeTokens(ctx, accessors(uuid.Generate()), false) 168 require.False(t, retryLater) 169 }) 170 171 t.Run("revoke token error", func(t *testing.T) { 172 exp := errors.New("consul broke") 173 ctx, c, token := setup(t, exp) 174 retryLater := c.RevokeTokens(ctx, accessors(token.AccessorID), false) 175 require.True(t, retryLater) 176 }) 177 } 178 179 func TestConsulACLsAPI_MarkForRevocation(t *testing.T) { 180 t.Parallel() 181 182 logger := testlog.HCLogger(t) 183 aclAPI := consul.NewMockACLsAPI(logger) 184 185 c := NewConsulACLsAPI(aclAPI, logger, nil) 186 187 generated, err := c.CreateToken(context.Background(), ServiceIdentityRequest{ 188 ClusterID: uuid.Generate(), 189 AllocID: uuid.Generate(), 190 TaskName: "task1-sidecar-proxy", 191 TaskKind: structs.NewTaskKind(structs.ConnectProxyPrefix, "service1"), 192 }) 193 require.NoError(t, err) 194 195 // set the mock error after calling CreateToken for setting up 196 aclAPI.SetError(nil) 197 198 accessors := []*structs.SITokenAccessor{{AccessorID: generated.AccessorID}} 199 c.MarkForRevocation(accessors) 200 require.Len(t, c.bgRetryRevocation, 1) 201 require.Contains(t, c.bgRetryRevocation, accessors[0]) 202 } 203 204 func TestConsulACLsAPI_bgRetryRevoke(t *testing.T) { 205 t.Parallel() 206 207 // manually create so the bg daemon does not run, letting us explicitly 208 // call and test bgRetryRevoke 209 setup := func(t *testing.T) (*consulACLsAPI, *mockPurgingServer) { 210 logger := testlog.HCLogger(t) 211 aclAPI := consul.NewMockACLsAPI(logger) 212 server := new(mockPurgingServer) 213 shortWait := rate.Limit(1 * time.Millisecond) 214 215 return &consulACLsAPI{ 216 aclClient: aclAPI, 217 purgeFunc: server.purgeFunc, 218 limiter: rate.NewLimiter(shortWait, int(shortWait)), 219 stopC: make(chan struct{}), 220 logger: logger, 221 }, server 222 } 223 224 t.Run("retry revoke no items", func(t *testing.T) { 225 c, server := setup(t) 226 c.bgRetryRevoke() 227 require.Empty(t, server) 228 }) 229 230 t.Run("retry revoke success", func(t *testing.T) { 231 c, server := setup(t) 232 accessorID := uuid.Generate() 233 c.bgRetryRevocation = append(c.bgRetryRevocation, &structs.SITokenAccessor{ 234 NodeID: uuid.Generate(), 235 AllocID: uuid.Generate(), 236 AccessorID: accessorID, 237 TaskName: "task1", 238 }) 239 require.Empty(t, server.purgedAccessorIDs) 240 c.bgRetryRevoke() 241 require.Equal(t, 1, len(server.purgedAccessorIDs)) 242 require.Equal(t, accessorID, server.purgedAccessorIDs[0]) 243 require.Empty(t, c.bgRetryRevocation) // should be empty now 244 }) 245 246 t.Run("retry revoke failure", func(t *testing.T) { 247 c, server := setup(t) 248 server.failure = errors.New("revocation fail") 249 accessorID := uuid.Generate() 250 c.bgRetryRevocation = append(c.bgRetryRevocation, &structs.SITokenAccessor{ 251 NodeID: uuid.Generate(), 252 AllocID: uuid.Generate(), 253 AccessorID: accessorID, 254 TaskName: "task1", 255 }) 256 require.Empty(t, server.purgedAccessorIDs) 257 c.bgRetryRevoke() 258 require.Equal(t, 1, len(c.bgRetryRevocation)) // non-empty because purge failed 259 require.Equal(t, accessorID, c.bgRetryRevocation[0].AccessorID) 260 }) 261 } 262 263 func TestConsulACLsAPI_Stop(t *testing.T) { 264 t.Parallel() 265 266 setup := func(t *testing.T) *consulACLsAPI { 267 logger := testlog.HCLogger(t) 268 return NewConsulACLsAPI(nil, logger, nil) 269 } 270 271 c := setup(t) 272 c.Stop() 273 _, err := c.CreateToken(context.Background(), ServiceIdentityRequest{ 274 ClusterID: "", 275 AllocID: "", 276 TaskName: "", 277 }) 278 require.Error(t, err) 279 } 280 281 func TestConsulACLsAPI_CheckSIPolicy(t *testing.T) { 282 t.Parallel() 283 284 try := func(t *testing.T, service, token string, expErr string) { 285 logger := testlog.HCLogger(t) 286 aclAPI := consul.NewMockACLsAPI(logger) 287 cAPI := NewConsulACLsAPI(aclAPI, logger, nil) 288 289 err := cAPI.CheckSIPolicy(context.Background(), service, token) 290 if expErr != "" { 291 require.EqualError(t, err, expErr) 292 } else { 293 require.NoError(t, err) 294 } 295 } 296 297 t.Run("operator has service write", func(t *testing.T) { 298 try(t, "service1", consul.ExampleOperatorTokenID1, "") 299 }) 300 301 t.Run("operator has service_prefix write", func(t *testing.T) { 302 try(t, "foo-service1", consul.ExampleOperatorTokenID2, "") 303 }) 304 305 t.Run("operator permissions insufficient", func(t *testing.T) { 306 try(t, "service1", consul.ExampleOperatorTokenID3, 307 "permission denied for \"service1\"", 308 ) 309 }) 310 311 t.Run("no token provided", func(t *testing.T) { 312 try(t, "service1", "", "missing consul token") 313 }) 314 315 t.Run("nonsense token provided", func(t *testing.T) { 316 try(t, "service1", "f1682bde-1e71-90b1-9204-85d35467ba61", 317 "unable to validate operator consul token: no such token", 318 ) 319 }) 320 }