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  }