github.com/hashicorp/vault/sdk@v0.11.0/helper/authmetadata/auth_metadata_acc_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package authmetadata 5 6 import ( 7 "context" 8 "fmt" 9 "reflect" 10 "testing" 11 12 "github.com/hashicorp/go-hclog" 13 "github.com/hashicorp/vault/sdk/framework" 14 "github.com/hashicorp/vault/sdk/logical" 15 ) 16 17 type environment struct { 18 ctx context.Context 19 storage logical.Storage 20 backend logical.Backend 21 } 22 23 func TestAcceptance(t *testing.T) { 24 ctx := context.Background() 25 storage := &logical.InmemStorage{} 26 b, err := backend(ctx, storage) 27 if err != nil { 28 t.Fatal(err) 29 } 30 env := &environment{ 31 ctx: ctx, 32 storage: storage, 33 backend: b, 34 } 35 t.Run("test initial fields are default", env.TestInitialFieldsAreDefault) 36 t.Run("test fields can be unset", env.TestAuthMetadataCanBeUnset) 37 t.Run("test defaults can be restored", env.TestDefaultCanBeReused) 38 t.Run("test default plus more cannot be selected", env.TestDefaultPlusMoreCannotBeSelected) 39 t.Run("test only non-defaults can be selected", env.TestOnlyNonDefaultsCanBeSelected) 40 t.Run("test bad field results in useful error", env.TestAddingBadField) 41 } 42 43 func (e *environment) TestInitialFieldsAreDefault(t *testing.T) { 44 // On the first read of auth_metadata, when nothing has been touched, 45 // we should receive the default field(s) if a read is performed. 46 resp, err := e.backend.HandleRequest(e.ctx, &logical.Request{ 47 Operation: logical.ReadOperation, 48 Path: "config", 49 Storage: e.storage, 50 Connection: &logical.Connection{ 51 RemoteAddr: "http://foo.com", 52 }, 53 }) 54 if err != nil { 55 t.Fatal(err) 56 } 57 if resp == nil || resp.Data == nil { 58 t.Fatal("expected non-nil response") 59 } 60 if !reflect.DeepEqual(resp.Data[authMetadataFields.FieldName], []string{"role_name"}) { 61 t.Fatal("expected default field of role_name to be returned") 62 } 63 64 // The auth should only have the default metadata. 65 resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ 66 Operation: logical.UpdateOperation, 67 Path: "login", 68 Storage: e.storage, 69 Connection: &logical.Connection{ 70 RemoteAddr: "http://foo.com", 71 }, 72 Data: map[string]interface{}{ 73 "role_name": "something", 74 }, 75 }) 76 if err != nil { 77 t.Fatal(err) 78 } 79 if resp == nil || resp.Auth == nil || resp.Auth.Alias == nil || resp.Auth.Alias.Metadata == nil { 80 t.Fatalf("expected alias metadata") 81 } 82 if len(resp.Auth.Alias.Metadata) != 1 { 83 t.Fatal("expected only 1 field") 84 } 85 if resp.Auth.Alias.Metadata["role_name"] != "something" { 86 t.Fatal("expected role_name to be something") 87 } 88 } 89 90 func (e *environment) TestAuthMetadataCanBeUnset(t *testing.T) { 91 // We should be able to set the auth_metadata to empty by sending an 92 // explicitly empty array. 93 resp, err := e.backend.HandleRequest(e.ctx, &logical.Request{ 94 Operation: logical.UpdateOperation, 95 Path: "config", 96 Storage: e.storage, 97 Connection: &logical.Connection{ 98 RemoteAddr: "http://foo.com", 99 }, 100 Data: map[string]interface{}{ 101 authMetadataFields.FieldName: []string{}, 102 }, 103 }) 104 if err != nil { 105 t.Fatal(err) 106 } 107 if resp != nil { 108 t.Fatal("expected nil response") 109 } 110 111 // Now we should receive no fields for auth_metadata. 112 resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ 113 Operation: logical.ReadOperation, 114 Path: "config", 115 Storage: e.storage, 116 Connection: &logical.Connection{ 117 RemoteAddr: "http://foo.com", 118 }, 119 }) 120 if err != nil { 121 t.Fatal(err) 122 } 123 if resp == nil || resp.Data == nil { 124 t.Fatal("expected non-nil response") 125 } 126 if !reflect.DeepEqual(resp.Data[authMetadataFields.FieldName], []string{}) { 127 t.Fatal("expected no fields to be returned") 128 } 129 130 // The auth should have no metadata. 131 resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ 132 Operation: logical.UpdateOperation, 133 Path: "login", 134 Storage: e.storage, 135 Connection: &logical.Connection{ 136 RemoteAddr: "http://foo.com", 137 }, 138 Data: map[string]interface{}{ 139 "role_name": "something", 140 }, 141 }) 142 if err != nil { 143 t.Fatal(err) 144 } 145 if resp == nil || resp.Auth == nil || resp.Auth.Alias == nil || resp.Auth.Alias.Metadata == nil { 146 t.Fatal("expected alias metadata") 147 } 148 if len(resp.Auth.Alias.Metadata) != 0 { 149 t.Fatal("expected 0 fields") 150 } 151 } 152 153 func (e *environment) TestDefaultCanBeReused(t *testing.T) { 154 // Now if we set it to "default", the default fields should 155 // be restored. 156 resp, err := e.backend.HandleRequest(e.ctx, &logical.Request{ 157 Operation: logical.UpdateOperation, 158 Path: "config", 159 Storage: e.storage, 160 Connection: &logical.Connection{ 161 RemoteAddr: "http://foo.com", 162 }, 163 Data: map[string]interface{}{ 164 authMetadataFields.FieldName: []string{"default"}, 165 }, 166 }) 167 if err != nil { 168 t.Fatal(err) 169 } 170 if resp != nil { 171 t.Fatal("expected nil response") 172 } 173 174 // Let's make sure we've returned to the default fields. 175 resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ 176 Operation: logical.ReadOperation, 177 Path: "config", 178 Storage: e.storage, 179 Connection: &logical.Connection{ 180 RemoteAddr: "http://foo.com", 181 }, 182 }) 183 if err != nil { 184 t.Fatal(err) 185 } 186 if resp == nil || resp.Data == nil { 187 t.Fatal("expected non-nil response") 188 } 189 if !reflect.DeepEqual(resp.Data[authMetadataFields.FieldName], []string{"role_name"}) { 190 t.Fatal("expected default field of role_name to be returned") 191 } 192 193 // We should again only receive the default field on the login. 194 resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ 195 Operation: logical.UpdateOperation, 196 Path: "login", 197 Storage: e.storage, 198 Connection: &logical.Connection{ 199 RemoteAddr: "http://foo.com", 200 }, 201 Data: map[string]interface{}{ 202 "role_name": "something", 203 }, 204 }) 205 if err != nil { 206 t.Fatal(err) 207 } 208 if resp == nil || resp.Auth == nil || resp.Auth.Alias == nil || resp.Auth.Alias.Metadata == nil { 209 t.Fatal("expected alias metadata") 210 } 211 if len(resp.Auth.Alias.Metadata) != 1 { 212 t.Fatal("expected only 1 field") 213 } 214 if resp.Auth.Alias.Metadata["role_name"] != "something" { 215 t.Fatal("expected role_name to be something") 216 } 217 } 218 219 func (e *environment) TestDefaultPlusMoreCannotBeSelected(t *testing.T) { 220 // We should not be able to set it to "default" plus 1 optional field. 221 _, err := e.backend.HandleRequest(e.ctx, &logical.Request{ 222 Operation: logical.UpdateOperation, 223 Path: "config", 224 Storage: e.storage, 225 Connection: &logical.Connection{ 226 RemoteAddr: "http://foo.com", 227 }, 228 Data: map[string]interface{}{ 229 authMetadataFields.FieldName: []string{"default", "remote_addr"}, 230 }, 231 }) 232 if err == nil { 233 t.Fatal("expected err") 234 } 235 } 236 237 func (e *environment) TestOnlyNonDefaultsCanBeSelected(t *testing.T) { 238 // Omit all default fields and just select one. 239 resp, err := e.backend.HandleRequest(e.ctx, &logical.Request{ 240 Operation: logical.UpdateOperation, 241 Path: "config", 242 Storage: e.storage, 243 Connection: &logical.Connection{ 244 RemoteAddr: "http://foo.com", 245 }, 246 Data: map[string]interface{}{ 247 authMetadataFields.FieldName: []string{"remote_addr"}, 248 }, 249 }) 250 if err != nil { 251 t.Fatal(err) 252 } 253 if resp != nil { 254 t.Fatal("expected nil response") 255 } 256 257 // Make sure that worked. 258 resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ 259 Operation: logical.ReadOperation, 260 Path: "config", 261 Storage: e.storage, 262 Connection: &logical.Connection{ 263 RemoteAddr: "http://foo.com", 264 }, 265 }) 266 if err != nil { 267 t.Fatal(err) 268 } 269 if resp == nil || resp.Data == nil { 270 t.Fatal("expected non-nil response") 271 } 272 if !reflect.DeepEqual(resp.Data[authMetadataFields.FieldName], []string{"remote_addr"}) { 273 t.Fatal("expected remote_addr to be returned") 274 } 275 276 // Ensure only the selected one is on logins. 277 // They both should now appear on the login. 278 resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ 279 Operation: logical.UpdateOperation, 280 Path: "login", 281 Storage: e.storage, 282 Connection: &logical.Connection{ 283 RemoteAddr: "http://foo.com", 284 }, 285 Data: map[string]interface{}{ 286 "role_name": "something", 287 }, 288 }) 289 if err != nil { 290 t.Fatal(err) 291 } 292 if resp == nil || resp.Auth == nil || resp.Auth.Alias == nil || resp.Auth.Alias.Metadata == nil { 293 t.Fatal("expected alias metadata") 294 } 295 if len(resp.Auth.Alias.Metadata) != 1 { 296 t.Fatal("expected only 1 field") 297 } 298 if resp.Auth.Alias.Metadata["remote_addr"] != "http://foo.com" { 299 t.Fatal("expected remote_addr to be http://foo.com") 300 } 301 } 302 303 func (e *environment) TestAddingBadField(t *testing.T) { 304 // Try adding an unsupported field. 305 resp, err := e.backend.HandleRequest(e.ctx, &logical.Request{ 306 Operation: logical.UpdateOperation, 307 Path: "config", 308 Storage: e.storage, 309 Connection: &logical.Connection{ 310 RemoteAddr: "http://foo.com", 311 }, 312 Data: map[string]interface{}{ 313 authMetadataFields.FieldName: []string{"asl;dfkj"}, 314 }, 315 }) 316 if err == nil { 317 t.Fatal("expected err") 318 } 319 if resp == nil { 320 t.Fatal("expected non-nil response") 321 } 322 if !resp.IsError() { 323 t.Fatal("expected error response") 324 } 325 } 326 327 // We expect people to embed the Handler on their 328 // config so it automatically makes its helper methods 329 // available and easy to find wherever the config is 330 // needed. Explicitly naming it in json avoids it 331 // automatically being named "Handler" by Go's JSON 332 // marshalling library. 333 type fakeConfig struct { 334 *Handler `json:"auth_metadata_handler"` 335 } 336 337 type fakeBackend struct { 338 *framework.Backend 339 } 340 341 // We expect each back-end to explicitly define the fields that 342 // will be included by default, and optionally available. 343 var authMetadataFields = &Fields{ 344 FieldName: "some_field_name", 345 Default: []string{ 346 "role_name", // This would likely never change because the alias is the role name. 347 }, 348 AvailableToAdd: []string{ 349 "remote_addr", // This would likely change with every new caller. 350 }, 351 } 352 353 func configPath() *framework.Path { 354 return &framework.Path{ 355 Pattern: "config", 356 Fields: map[string]*framework.FieldSchema{ 357 authMetadataFields.FieldName: FieldSchema(authMetadataFields), 358 }, 359 Operations: map[logical.Operation]framework.OperationHandler{ 360 logical.ReadOperation: &framework.PathOperation{ 361 Callback: func(ctx context.Context, req *logical.Request, fd *framework.FieldData) (*logical.Response, error) { 362 entryRaw, err := req.Storage.Get(ctx, "config") 363 if err != nil { 364 return nil, err 365 } 366 conf := &fakeConfig{ 367 Handler: NewHandler(authMetadataFields), 368 } 369 if entryRaw != nil { 370 if err := entryRaw.DecodeJSON(conf); err != nil { 371 return nil, err 372 } 373 } 374 // Note that even if the config entry was nil, we return 375 // a populated response to give info on what the default 376 // auth metadata is when unconfigured. 377 return &logical.Response{ 378 Data: map[string]interface{}{ 379 authMetadataFields.FieldName: conf.AuthMetadata(), 380 }, 381 }, nil 382 }, 383 }, 384 logical.UpdateOperation: &framework.PathOperation{ 385 Callback: func(ctx context.Context, req *logical.Request, fd *framework.FieldData) (*logical.Response, error) { 386 entryRaw, err := req.Storage.Get(ctx, "config") 387 if err != nil { 388 return nil, err 389 } 390 conf := &fakeConfig{ 391 Handler: NewHandler(authMetadataFields), 392 } 393 if entryRaw != nil { 394 if err := entryRaw.DecodeJSON(conf); err != nil { 395 return nil, err 396 } 397 } 398 // This is where we read in the user's given auth metadata. 399 if err := conf.ParseAuthMetadata(fd); err != nil { 400 // Since this will only error on bad input, it's best to give 401 // a 400 response with the explicit problem included. 402 return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest 403 } 404 entry, err := logical.StorageEntryJSON("config", conf) 405 if err != nil { 406 return nil, err 407 } 408 if err = req.Storage.Put(ctx, entry); err != nil { 409 return nil, err 410 } 411 return nil, nil 412 }, 413 }, 414 }, 415 } 416 } 417 418 func loginPath() *framework.Path { 419 return &framework.Path{ 420 Pattern: "login", 421 Fields: map[string]*framework.FieldSchema{ 422 "role_name": { 423 Type: framework.TypeString, 424 Required: true, 425 }, 426 }, 427 Operations: map[logical.Operation]framework.OperationHandler{ 428 logical.UpdateOperation: &framework.PathOperation{ 429 Callback: func(ctx context.Context, req *logical.Request, fd *framework.FieldData) (*logical.Response, error) { 430 entryRaw, err := req.Storage.Get(ctx, "config") 431 if err != nil { 432 return nil, err 433 } 434 conf := &fakeConfig{ 435 Handler: NewHandler(authMetadataFields), 436 } 437 if entryRaw != nil { 438 if err := entryRaw.DecodeJSON(conf); err != nil { 439 return nil, err 440 } 441 } 442 auth := &logical.Auth{ 443 Alias: &logical.Alias{ 444 Name: fd.Get("role_name").(string), 445 }, 446 } 447 // Here we provide everything and let the method strip out 448 // the undesired stuff. 449 if err := conf.PopulateDesiredMetadata(auth, map[string]string{ 450 "role_name": fd.Get("role_name").(string), 451 "remote_addr": req.Connection.RemoteAddr, 452 }); err != nil { 453 fmt.Println("unable to populate due to " + err.Error()) 454 } 455 return &logical.Response{ 456 Auth: auth, 457 }, nil 458 }, 459 }, 460 }, 461 } 462 } 463 464 func backend(ctx context.Context, storage logical.Storage) (logical.Backend, error) { 465 b := &fakeBackend{ 466 Backend: &framework.Backend{ 467 Paths: []*framework.Path{ 468 configPath(), 469 loginPath(), 470 }, 471 }, 472 } 473 if err := b.Setup(ctx, &logical.BackendConfig{ 474 StorageView: storage, 475 Logger: hclog.Default(), 476 }); err != nil { 477 return nil, err 478 } 479 return b, nil 480 }