github.com/thomasobenaus/nomad@v0.11.1/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, _ ServiceIdentityIndex) (*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 m.lock.Lock() 69 defer m.lock.Unlock() 70 71 for _, accessor := range accessors { 72 m.revokeRequests = append(m.revokeRequests, revokeRequest{ 73 accessorID: accessor.AccessorID, 74 committed: committed, 75 }) 76 } 77 return false 78 } 79 80 func TestConsulACLsAPI_CreateToken(t *testing.T) { 81 t.Parallel() 82 83 try := func(t *testing.T, expErr error) { 84 logger := testlog.HCLogger(t) 85 aclAPI := consul.NewMockACLsAPI(logger) 86 aclAPI.SetError(expErr) 87 88 c := NewConsulACLsAPI(aclAPI, logger, nil) 89 90 ctx := context.Background() 91 sii := ServiceIdentityIndex{ 92 AllocID: uuid.Generate(), 93 ClusterID: uuid.Generate(), 94 TaskName: "my-task1", 95 } 96 97 token, err := c.CreateToken(ctx, sii) 98 99 if expErr != nil { 100 require.Equal(t, expErr, err) 101 require.Nil(t, token) 102 } else { 103 require.NoError(t, err) 104 require.Equal(t, "my-task1", token.TaskName) 105 require.True(t, helper.IsUUID(token.AccessorID)) 106 require.True(t, helper.IsUUID(token.SecretID)) 107 } 108 } 109 110 t.Run("create token success", func(t *testing.T) { 111 try(t, nil) 112 }) 113 114 t.Run("create token error", func(t *testing.T) { 115 try(t, errors.New("consul broke")) 116 }) 117 } 118 119 func TestConsulACLsAPI_RevokeTokens(t *testing.T) { 120 t.Parallel() 121 122 setup := func(t *testing.T, exp error) (context.Context, ConsulACLsAPI, *structs.SIToken) { 123 logger := testlog.HCLogger(t) 124 aclAPI := consul.NewMockACLsAPI(logger) 125 126 c := NewConsulACLsAPI(aclAPI, logger, nil) 127 128 ctx := context.Background() 129 generated, err := c.CreateToken(ctx, ServiceIdentityIndex{ 130 ClusterID: uuid.Generate(), 131 AllocID: uuid.Generate(), 132 TaskName: "task1", 133 }) 134 require.NoError(t, err) 135 136 // set the mock error after calling CreateToken for setting up 137 aclAPI.SetError(exp) 138 139 return context.Background(), c, generated 140 } 141 142 accessors := func(ids ...string) (result []*structs.SITokenAccessor) { 143 for _, id := range ids { 144 result = append(result, &structs.SITokenAccessor{AccessorID: id}) 145 } 146 return 147 } 148 149 t.Run("revoke token success", func(t *testing.T) { 150 ctx, c, token := setup(t, nil) 151 retryLater := c.RevokeTokens(ctx, accessors(token.AccessorID), false) 152 require.False(t, retryLater) 153 }) 154 155 t.Run("revoke token non-existent", func(t *testing.T) { 156 ctx, c, _ := setup(t, nil) 157 retryLater := c.RevokeTokens(ctx, accessors(uuid.Generate()), false) 158 require.False(t, retryLater) 159 }) 160 161 t.Run("revoke token error", func(t *testing.T) { 162 exp := errors.New("consul broke") 163 ctx, c, token := setup(t, exp) 164 retryLater := c.RevokeTokens(ctx, accessors(token.AccessorID), false) 165 require.True(t, retryLater) 166 }) 167 } 168 169 func TestConsulACLsAPI_bgRetryRevoke(t *testing.T) { 170 t.Parallel() 171 172 // manually create so the bg daemon does not run, letting us explicitly 173 // call and test bgRetryRevoke 174 setup := func(t *testing.T) (*consulACLsAPI, *mockPurgingServer) { 175 logger := testlog.HCLogger(t) 176 aclAPI := consul.NewMockACLsAPI(logger) 177 server := new(mockPurgingServer) 178 shortWait := rate.Limit(1 * time.Millisecond) 179 180 return &consulACLsAPI{ 181 aclClient: aclAPI, 182 purgeFunc: server.purgeFunc, 183 limiter: rate.NewLimiter(shortWait, int(shortWait)), 184 stopC: make(chan struct{}), 185 logger: logger, 186 }, server 187 } 188 189 t.Run("retry revoke no items", func(t *testing.T) { 190 c, server := setup(t) 191 c.bgRetryRevoke() 192 require.Empty(t, server) 193 }) 194 195 t.Run("retry revoke success", func(t *testing.T) { 196 c, server := setup(t) 197 accessorID := uuid.Generate() 198 c.bgRetryRevocation = append(c.bgRetryRevocation, &structs.SITokenAccessor{ 199 NodeID: uuid.Generate(), 200 AllocID: uuid.Generate(), 201 AccessorID: accessorID, 202 TaskName: "task1", 203 }) 204 require.Empty(t, server.purgedAccessorIDs) 205 c.bgRetryRevoke() 206 require.Equal(t, 1, len(server.purgedAccessorIDs)) 207 require.Equal(t, accessorID, server.purgedAccessorIDs[0]) 208 require.Empty(t, c.bgRetryRevocation) // should be empty now 209 }) 210 211 t.Run("retry revoke failure", func(t *testing.T) { 212 c, server := setup(t) 213 server.failure = errors.New("revocation fail") 214 accessorID := uuid.Generate() 215 c.bgRetryRevocation = append(c.bgRetryRevocation, &structs.SITokenAccessor{ 216 NodeID: uuid.Generate(), 217 AllocID: uuid.Generate(), 218 AccessorID: accessorID, 219 TaskName: "task1", 220 }) 221 require.Empty(t, server.purgedAccessorIDs) 222 c.bgRetryRevoke() 223 require.Equal(t, 1, len(c.bgRetryRevocation)) // non-empty because purge failed 224 require.Equal(t, accessorID, c.bgRetryRevocation[0].AccessorID) 225 }) 226 } 227 228 func TestConsulACLsAPI_Stop(t *testing.T) { 229 t.Parallel() 230 231 setup := func(t *testing.T) *consulACLsAPI { 232 logger := testlog.HCLogger(t) 233 return NewConsulACLsAPI(nil, logger, nil) 234 } 235 236 c := setup(t) 237 c.Stop() 238 _, err := c.CreateToken(context.Background(), ServiceIdentityIndex{ 239 ClusterID: "", 240 AllocID: "", 241 TaskName: "", 242 }) 243 require.Error(t, err) 244 } 245 246 func TestConsulACLsAPI_CheckSIPolicy(t *testing.T) { 247 t.Parallel() 248 249 try := func(t *testing.T, service, token string, expErr string) { 250 logger := testlog.HCLogger(t) 251 aclAPI := consul.NewMockACLsAPI(logger) 252 cAPI := NewConsulACLsAPI(aclAPI, logger, nil) 253 254 err := cAPI.CheckSIPolicy(context.Background(), service, token) 255 if expErr != "" { 256 require.EqualError(t, err, expErr) 257 } else { 258 require.NoError(t, err) 259 } 260 } 261 262 t.Run("operator has service write", func(t *testing.T) { 263 try(t, "service1", consul.ExampleOperatorTokenID1, "") 264 }) 265 266 t.Run("operator has service_prefix write", func(t *testing.T) { 267 try(t, "foo-service1", consul.ExampleOperatorTokenID2, "") 268 }) 269 270 t.Run("operator permissions insufficient", func(t *testing.T) { 271 try(t, "service1", consul.ExampleOperatorTokenID3, 272 "permission denied for \"service1\"", 273 ) 274 }) 275 276 t.Run("no token provided", func(t *testing.T) { 277 try(t, "service1", "", "missing consul token") 278 }) 279 280 t.Run("nonsense token provided", func(t *testing.T) { 281 try(t, "service1", "f1682bde-1e71-90b1-9204-85d35467ba61", 282 "unable to validate operator consul token: no such token", 283 ) 284 }) 285 }