Merge branch 'master' into jo-ie11-fixes
This commit is contained in:
commit
7bb9490403
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -1,3 +1,20 @@
|
|||
## 0.10.1 (Unreleased)
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* identity: Add the ability to disable an entity. Disabling an entity does not
|
||||
revoke associated tokens, but while the entity is disabled they cannot be
|
||||
used. [GH-4353]
|
||||
* auth/token: Add to the token lookup response, the policies inherited due to
|
||||
identity associations [GH-4366]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* identity: Persist entity memberships in external identity groups across
|
||||
mounts [GH-4365]
|
||||
* secret/gcp: Fix panic on rollback when a roleset wasn't created properly
|
||||
[GH-4344]
|
||||
|
||||
## 0.10.0 (April 10th, 2018)
|
||||
|
||||
SECURITY:
|
||||
|
|
|
@ -606,7 +606,7 @@ CLUSTER_SYNTHESIS_COMPLETE:
|
|||
// Force https as we'll always be TLS-secured
|
||||
u, err := url.ParseRequestURI(coreConfig.ClusterAddr)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error parsing cluster address %s: %v", coreConfig.RedirectAddr, err))
|
||||
c.UI.Error(fmt.Sprintf("Error parsing cluster address %s: %v", coreConfig.ClusterAddr, err))
|
||||
return 11
|
||||
}
|
||||
u.Scheme = "https"
|
||||
|
|
|
@ -200,6 +200,9 @@ type Entity struct {
|
|||
// the entities belonging to a particular bucket during invalidation of the
|
||||
// storage key.
|
||||
BucketKeyHash string `sentinel:"" protobuf:"bytes,9,opt,name=bucket_key_hash,json=bucketKeyHash" json:"bucket_key_hash,omitempty"`
|
||||
// Disabled indicates whether tokens associated with the account should not
|
||||
// be able to be used
|
||||
Disabled bool `sentinel:"" protobuf:"varint,11,opt,name=disabled" json:"disabled,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Entity) Reset() { *m = Entity{} }
|
||||
|
@ -270,6 +273,13 @@ func (m *Entity) GetBucketKeyHash() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (m *Entity) GetDisabled() bool {
|
||||
if m != nil {
|
||||
return m.Disabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Alias represents the alias that gets stored inside of the
|
||||
// entity object in storage and also represents in an in-memory index of an
|
||||
// alias object.
|
||||
|
@ -392,43 +402,44 @@ func init() {
|
|||
func init() { proto.RegisterFile("types.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 603 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x93, 0xdd, 0x6e, 0xd3, 0x30,
|
||||
0x14, 0xc7, 0xd5, 0xa6, 0x9f, 0x27, 0x5d, 0x37, 0x2c, 0x84, 0x4c, 0xa5, 0x41, 0x37, 0x69, 0x28,
|
||||
0x70, 0x91, 0x49, 0xe3, 0x86, 0x8d, 0x0b, 0x34, 0xc1, 0x80, 0x09, 0x21, 0xa1, 0x68, 0x5c, 0x47,
|
||||
0x6e, 0xe2, 0xb5, 0xd6, 0x92, 0x38, 0x8a, 0x1d, 0x44, 0x5e, 0x87, 0x97, 0xe1, 0x69, 0x78, 0x07,
|
||||
0xe4, 0xe3, 0xa6, 0x0d, 0x74, 0x7c, 0x4c, 0xdb, 0x9d, 0xf3, 0x3f, 0xc7, 0xc7, 0x27, 0xe7, 0xff,
|
||||
0x3b, 0xe0, 0xea, 0x2a, 0xe7, 0xca, 0xcf, 0x0b, 0xa9, 0x25, 0x19, 0x88, 0x98, 0x67, 0x5a, 0xe8,
|
||||
0x6a, 0xf2, 0x78, 0x2e, 0xe5, 0x3c, 0xe1, 0x87, 0xa8, 0xcf, 0xca, 0xcb, 0x43, 0x2d, 0x52, 0xae,
|
||||
0x34, 0x4b, 0x73, 0x9b, 0xba, 0xff, 0xad, 0x03, 0xdd, 0x77, 0x85, 0x2c, 0x73, 0x32, 0x86, 0xb6,
|
||||
0x88, 0x69, 0x6b, 0xda, 0xf2, 0x86, 0x41, 0x5b, 0xc4, 0x84, 0x40, 0x27, 0x63, 0x29, 0xa7, 0x6d,
|
||||
0x54, 0xf0, 0x4c, 0x26, 0x30, 0xc8, 0x65, 0x22, 0x22, 0xc1, 0x15, 0x75, 0xa6, 0x8e, 0x37, 0x0c,
|
||||
0x56, 0xdf, 0xc4, 0x83, 0x9d, 0x9c, 0x15, 0x3c, 0xd3, 0xe1, 0xdc, 0xd4, 0x0b, 0x45, 0xac, 0x68,
|
||||
0x07, 0x73, 0xc6, 0x56, 0xc7, 0x67, 0xce, 0x63, 0x45, 0x9e, 0xc1, 0xbd, 0x94, 0xa7, 0x33, 0x5e,
|
||||
0x84, 0xb6, 0x4b, 0x4c, 0xed, 0x62, 0xea, 0xb6, 0x0d, 0x9c, 0xa1, 0x6e, 0x72, 0x8f, 0x61, 0x90,
|
||||
0x72, 0xcd, 0x62, 0xa6, 0x19, 0xed, 0x4d, 0x1d, 0xcf, 0x3d, 0xda, 0xf5, 0xeb, 0xbf, 0xf3, 0xb1,
|
||||
0xa2, 0xff, 0x71, 0x19, 0x3f, 0xcb, 0x74, 0x51, 0x05, 0xab, 0x74, 0xf2, 0x0a, 0xb6, 0xa2, 0x82,
|
||||
0x33, 0x2d, 0x64, 0x16, 0x9a, 0xdf, 0xa6, 0xfd, 0x69, 0xcb, 0x73, 0x8f, 0x26, 0xbe, 0x9d, 0x89,
|
||||
0x5f, 0xcf, 0xc4, 0xbf, 0xa8, 0x67, 0x12, 0x8c, 0xea, 0x0b, 0x46, 0x22, 0x6f, 0x60, 0x27, 0x61,
|
||||
0x4a, 0x87, 0x65, 0x1e, 0x33, 0xcd, 0x6d, 0x8d, 0xc1, 0x3f, 0x6b, 0x8c, 0xcd, 0x9d, 0xcf, 0x78,
|
||||
0x05, 0xab, 0xec, 0xc1, 0x28, 0x95, 0xb1, 0xb8, 0xac, 0x42, 0x91, 0xc5, 0xfc, 0x2b, 0x1d, 0x4e,
|
||||
0x5b, 0x5e, 0x27, 0x70, 0xad, 0x76, 0x6e, 0x24, 0xf2, 0x04, 0xb6, 0x67, 0x65, 0x74, 0xc5, 0x75,
|
||||
0x78, 0xc5, 0xab, 0x70, 0xc1, 0xd4, 0x82, 0x02, 0x4e, 0x7d, 0xcb, 0xca, 0x1f, 0x78, 0xf5, 0x9e,
|
||||
0xa9, 0x05, 0x39, 0x80, 0x2e, 0x4b, 0x04, 0x53, 0xd4, 0xc5, 0x2e, 0xb6, 0xd7, 0x93, 0x38, 0x35,
|
||||
0x72, 0x60, 0xa3, 0xc6, 0x39, 0x43, 0x03, 0x1d, 0x59, 0xe7, 0xcc, 0x79, 0xf2, 0x12, 0xb6, 0x7e,
|
||||
0x99, 0x13, 0xd9, 0x01, 0xe7, 0x8a, 0x57, 0x4b, 0xbf, 0xcd, 0x91, 0xdc, 0x87, 0xee, 0x17, 0x96,
|
||||
0x94, 0xb5, 0xe3, 0xf6, 0xe3, 0xa4, 0xfd, 0xa2, 0xb5, 0xff, 0xdd, 0x81, 0x9e, 0xb5, 0x84, 0x3c,
|
||||
0x85, 0x3e, 0x3e, 0xc2, 0x15, 0x6d, 0xa1, 0x1d, 0x1b, 0x4d, 0xd4, 0xf1, 0x25, 0x50, 0xed, 0x0d,
|
||||
0xa0, 0x9c, 0x06, 0x50, 0x27, 0x0d, 0x7b, 0x3b, 0x58, 0xef, 0xd1, 0xba, 0x9e, 0x7d, 0xf2, 0xff,
|
||||
0xfd, 0xed, 0xde, 0x81, 0xbf, 0xbd, 0x1b, 0xfb, 0x8b, 0x34, 0x17, 0x73, 0x1e, 0x37, 0x69, 0xee,
|
||||
0xd7, 0x34, 0x9b, 0xc0, 0x9a, 0xe6, 0xe6, 0xfe, 0x0c, 0x7e, 0xdb, 0x9f, 0x6b, 0x20, 0x18, 0x5e,
|
||||
0x03, 0xc1, 0xed, 0x9c, 0xfc, 0xe1, 0x40, 0x17, 0x6d, 0xda, 0x58, 0xf7, 0x3d, 0x18, 0x45, 0x2c,
|
||||
0x93, 0x99, 0x88, 0x58, 0x12, 0xae, 0x7c, 0x73, 0x57, 0xda, 0x79, 0x4c, 0x76, 0x01, 0x52, 0x59,
|
||||
0x66, 0x3a, 0x44, 0xba, 0xac, 0x8d, 0x43, 0x54, 0x2e, 0xaa, 0x9c, 0x93, 0x03, 0x18, 0xdb, 0x30,
|
||||
0x8b, 0x22, 0xae, 0x94, 0x2c, 0x68, 0xc7, 0xf6, 0x8f, 0xea, 0xe9, 0x52, 0x5c, 0x57, 0xc9, 0x99,
|
||||
0x5e, 0xa0, 0x67, 0x75, 0x95, 0x4f, 0x4c, 0x2f, 0xfe, 0xbe, 0xf0, 0xd8, 0xfa, 0x1f, 0x81, 0xa8,
|
||||
0x01, 0xeb, 0x37, 0x00, 0xdb, 0x80, 0x64, 0x70, 0x07, 0x90, 0x0c, 0x6f, 0x0c, 0xc9, 0x31, 0x3c,
|
||||
0x5c, 0x42, 0x72, 0x59, 0xc8, 0x34, 0x6c, 0x4e, 0x5a, 0x51, 0x40, 0x12, 0x1e, 0xd8, 0x84, 0xb7,
|
||||
0x85, 0x4c, 0x5f, 0xaf, 0x87, 0xae, 0x6e, 0xe5, 0xf7, 0xac, 0x87, 0xbd, 0x3d, 0xff, 0x19, 0x00,
|
||||
0x00, 0xff, 0xff, 0x8e, 0x4a, 0xc5, 0xdb, 0x1f, 0x06, 0x00, 0x00,
|
||||
// 617 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x94, 0xdd, 0x6e, 0xd3, 0x30,
|
||||
0x14, 0xc7, 0xd5, 0xa6, 0x1f, 0xe9, 0x69, 0xd7, 0x0d, 0x0b, 0x21, 0x53, 0x69, 0xd0, 0x4d, 0x1a,
|
||||
0x2a, 0x5c, 0x64, 0xd2, 0xb8, 0x61, 0xe3, 0x02, 0x4d, 0x30, 0x60, 0x42, 0x48, 0x28, 0x1a, 0xd7,
|
||||
0x91, 0x1b, 0x7b, 0xad, 0xb5, 0x24, 0x8e, 0x62, 0x17, 0x91, 0xd7, 0xe1, 0xd5, 0xb8, 0xe6, 0x1d,
|
||||
0x90, 0x8f, 0x9b, 0x36, 0xd0, 0xf1, 0x31, 0x6d, 0x77, 0xf6, 0xff, 0x1c, 0x1f, 0x1f, 0x9f, 0xff,
|
||||
0x2f, 0x81, 0xbe, 0x29, 0x73, 0xa1, 0x83, 0xbc, 0x50, 0x46, 0x11, 0x5f, 0x72, 0x91, 0x19, 0x69,
|
||||
0xca, 0xd1, 0xe3, 0x99, 0x52, 0xb3, 0x44, 0x1c, 0xa2, 0x3e, 0x5d, 0x5c, 0x1e, 0x1a, 0x99, 0x0a,
|
||||
0x6d, 0x58, 0x9a, 0xbb, 0xd4, 0xfd, 0x6f, 0x2d, 0x68, 0xbf, 0x2b, 0xd4, 0x22, 0x27, 0x43, 0x68,
|
||||
0x4a, 0x4e, 0x1b, 0xe3, 0xc6, 0xa4, 0x17, 0x36, 0x25, 0x27, 0x04, 0x5a, 0x19, 0x4b, 0x05, 0x6d,
|
||||
0xa2, 0x82, 0x6b, 0x32, 0x02, 0x3f, 0x57, 0x89, 0x8c, 0xa5, 0xd0, 0xd4, 0x1b, 0x7b, 0x93, 0x5e,
|
||||
0xb8, 0xda, 0x93, 0x09, 0xec, 0xe4, 0xac, 0x10, 0x99, 0x89, 0x66, 0xb6, 0x5e, 0x24, 0xb9, 0xa6,
|
||||
0x2d, 0xcc, 0x19, 0x3a, 0x1d, 0xaf, 0x39, 0xe7, 0x9a, 0x3c, 0x83, 0x7b, 0xa9, 0x48, 0xa7, 0xa2,
|
||||
0x88, 0x5c, 0x97, 0x98, 0xda, 0xc6, 0xd4, 0x6d, 0x17, 0x38, 0x43, 0xdd, 0xe6, 0x1e, 0x83, 0x9f,
|
||||
0x0a, 0xc3, 0x38, 0x33, 0x8c, 0x76, 0xc6, 0xde, 0xa4, 0x7f, 0xb4, 0x1b, 0x54, 0xaf, 0x0b, 0xb0,
|
||||
0x62, 0xf0, 0x71, 0x19, 0x3f, 0xcb, 0x4c, 0x51, 0x86, 0xab, 0x74, 0xf2, 0x0a, 0xb6, 0xe2, 0x42,
|
||||
0x30, 0x23, 0x55, 0x16, 0xd9, 0x67, 0xd3, 0xee, 0xb8, 0x31, 0xe9, 0x1f, 0x8d, 0x02, 0x37, 0x93,
|
||||
0xa0, 0x9a, 0x49, 0x70, 0x51, 0xcd, 0x24, 0x1c, 0x54, 0x07, 0xac, 0x44, 0xde, 0xc0, 0x4e, 0xc2,
|
||||
0xb4, 0x89, 0x16, 0x39, 0x67, 0x46, 0xb8, 0x1a, 0xfe, 0x3f, 0x6b, 0x0c, 0xed, 0x99, 0xcf, 0x78,
|
||||
0x04, 0xab, 0xec, 0xc1, 0x20, 0x55, 0x5c, 0x5e, 0x96, 0x91, 0xcc, 0xb8, 0xf8, 0x4a, 0x7b, 0xe3,
|
||||
0xc6, 0xa4, 0x15, 0xf6, 0x9d, 0x76, 0x6e, 0x25, 0xf2, 0x04, 0xb6, 0xa7, 0x8b, 0xf8, 0x4a, 0x98,
|
||||
0xe8, 0x4a, 0x94, 0xd1, 0x9c, 0xe9, 0x39, 0x05, 0x9c, 0xfa, 0x96, 0x93, 0x3f, 0x88, 0xf2, 0x3d,
|
||||
0xd3, 0x73, 0x72, 0x00, 0x6d, 0x96, 0x48, 0xa6, 0x69, 0x1f, 0xbb, 0xd8, 0x5e, 0x4f, 0xe2, 0xd4,
|
||||
0xca, 0xa1, 0x8b, 0x5a, 0xe7, 0x2c, 0x0d, 0x74, 0xe0, 0x9c, 0xb3, 0xeb, 0xd1, 0x4b, 0xd8, 0xfa,
|
||||
0x65, 0x4e, 0x64, 0x07, 0xbc, 0x2b, 0x51, 0x2e, 0xfd, 0xb6, 0x4b, 0x72, 0x1f, 0xda, 0x5f, 0x58,
|
||||
0xb2, 0xa8, 0x1c, 0x77, 0x9b, 0x93, 0xe6, 0x8b, 0xc6, 0xfe, 0x77, 0x0f, 0x3a, 0xce, 0x12, 0xf2,
|
||||
0x14, 0xba, 0x78, 0x89, 0xd0, 0xb4, 0x81, 0x76, 0x6c, 0x34, 0x51, 0xc5, 0x97, 0x40, 0x35, 0x37,
|
||||
0x80, 0xf2, 0x6a, 0x40, 0x9d, 0xd4, 0xec, 0x6d, 0x61, 0xbd, 0x47, 0xeb, 0x7a, 0xee, 0xca, 0xff,
|
||||
0xf7, 0xb7, 0x7d, 0x07, 0xfe, 0x76, 0x6e, 0xec, 0x2f, 0xd2, 0x5c, 0xcc, 0x04, 0xaf, 0xd3, 0xdc,
|
||||
0xad, 0x68, 0xb6, 0x81, 0x35, 0xcd, 0xf5, 0xef, 0xc7, 0xff, 0xed, 0xfb, 0xb9, 0x06, 0x82, 0xde,
|
||||
0x75, 0x10, 0x8c, 0xc0, 0xe7, 0x52, 0xb3, 0x69, 0x22, 0x38, 0x72, 0xe0, 0x87, 0xab, 0xfd, 0xed,
|
||||
0x5c, 0xfe, 0xe1, 0x41, 0x1b, 0x2d, 0xdc, 0xf8, 0x15, 0xec, 0xc1, 0x20, 0x66, 0x99, 0xca, 0x64,
|
||||
0xcc, 0x92, 0x68, 0xe5, 0x69, 0x7f, 0xa5, 0x9d, 0x73, 0xb2, 0x0b, 0x90, 0xaa, 0x45, 0x66, 0x22,
|
||||
0x24, 0xcf, 0x59, 0xdc, 0x43, 0xe5, 0xa2, 0xcc, 0x05, 0x39, 0x80, 0xa1, 0x0b, 0xb3, 0x38, 0x16,
|
||||
0x5a, 0xab, 0x82, 0xb6, 0xdc, 0xdb, 0x50, 0x3d, 0x5d, 0x8a, 0xeb, 0x2a, 0x39, 0x33, 0x73, 0xf4,
|
||||
0xb3, 0xaa, 0xf2, 0x89, 0x99, 0xf9, 0xdf, 0x7f, 0x06, 0xd8, 0xfa, 0x1f, 0x61, 0xa9, 0xe0, 0xeb,
|
||||
0xd6, 0xe0, 0xdb, 0x00, 0xc8, 0xbf, 0x03, 0x80, 0x7a, 0x37, 0x06, 0xe8, 0x18, 0x1e, 0x2e, 0x01,
|
||||
0xba, 0x2c, 0x54, 0x1a, 0xd5, 0x27, 0xad, 0x29, 0x20, 0x25, 0x0f, 0x5c, 0xc2, 0xdb, 0x42, 0xa5,
|
||||
0xaf, 0xd7, 0x43, 0xd7, 0xb7, 0xf2, 0x7b, 0xda, 0xc1, 0xde, 0x9e, 0xff, 0x0c, 0x00, 0x00, 0xff,
|
||||
0xff, 0x60, 0x0b, 0xc9, 0x74, 0x3b, 0x06, 0x00, 0x00,
|
||||
}
|
||||
|
|
|
@ -110,6 +110,10 @@ message Entity {
|
|||
// MFASecrets holds the MFA secrets indexed by the identifier of the MFA
|
||||
// method configuration.
|
||||
//map<string, mfa.Secret> mfa_secrets = 10;
|
||||
|
||||
// Disabled indicates whether tokens associated with the account should not
|
||||
// be able to be used
|
||||
bool disabled = 11;
|
||||
}
|
||||
|
||||
// Alias represents the alias that gets stored inside of the
|
||||
|
|
|
@ -273,6 +273,10 @@ var (
|
|||
// ErrPermissionDenied is returned if the client is not authorized
|
||||
ErrPermissionDenied = errors.New("permission denied")
|
||||
|
||||
// ErrDisabledEntity is returned if the entity tied to a token is marked as
|
||||
// disabled
|
||||
ErrEntityDisabled = errors.New("entity associated with token is disabled")
|
||||
|
||||
// ErrMultiAuthzPending is returned if the the request needs more
|
||||
// authorizations
|
||||
ErrMultiAuthzPending = errors.New("request needs further approval")
|
||||
|
|
|
@ -12,17 +12,37 @@ export default Ember.Component.extend({
|
|||
return `${this.get('filename')}-${new Date().toISOString()}.${this.get('extension')}`;
|
||||
}),
|
||||
|
||||
href: computed('data', 'mime', 'stringify', function() {
|
||||
fileLike: computed('data', 'mime', 'strigify', 'download', function() {
|
||||
let file;
|
||||
let data = this.get('data');
|
||||
const mime = this.get('mime');
|
||||
let filename = this.get('download');
|
||||
let mime = this.get('mime');
|
||||
if (this.get('stringify')) {
|
||||
data = JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
const file = new File([data], { type: mime });
|
||||
return window.URL.createObjectURL(file);
|
||||
if (window.navigator.msSaveOrOpenBlob) {
|
||||
file = new Blob([data], { type: mime });
|
||||
file.name = filename;
|
||||
} else {
|
||||
file = new File([data], filename, { type: mime });
|
||||
}
|
||||
return file;
|
||||
}),
|
||||
|
||||
href: computed('fileLike', function() {
|
||||
return window.URL.createObjectURL(this.get('fileLike'));
|
||||
}),
|
||||
|
||||
click(event) {
|
||||
if (!window.navigator.msSaveOrOpenBlob) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
let file = this.get('fileLike');
|
||||
//lol whyyyy
|
||||
window.navigator.msSaveOrOpenBlob(file, file.name);
|
||||
},
|
||||
|
||||
actionText: 'Download',
|
||||
data: null,
|
||||
filename: null,
|
||||
|
|
|
@ -8,13 +8,13 @@ const { computed } = Ember;
|
|||
export default Ember.Controller.extend({
|
||||
mountTypes: [
|
||||
{ label: 'AWS', value: 'aws' },
|
||||
{ label: 'Cassandra', value: 'cassandra' },
|
||||
{ label: 'Cassandra', value: 'cassandra', deprecated: true },
|
||||
{ label: 'Consul', value: 'consul' },
|
||||
{ label: 'Databases', value: 'database' },
|
||||
{ label: 'Google Cloud', value: 'gcp' },
|
||||
{ label: 'KV', value: 'kv' },
|
||||
{ label: 'MongoDB', value: 'mongodb' },
|
||||
{ label: 'MS SQL', value: 'mssql', deprecated: true },
|
||||
{ label: 'MongoDB', value: 'mongodb', deprecated: true },
|
||||
{ label: 'MSSQL', value: 'mssql', deprecated: true },
|
||||
{ label: 'MySQL', value: 'mysql', deprecated: true },
|
||||
{ label: 'Nomad', value: 'nomad' },
|
||||
{ label: 'PKI', value: 'pki' },
|
||||
|
|
|
@ -46,7 +46,7 @@ export default EditBase.extend({
|
|||
const parentKey = params.secret ? params.secret : '';
|
||||
return Ember.RSVP.hash({
|
||||
secret: this.createModel(transition, parentKey),
|
||||
capabilities: this.capabilities(parentKey),
|
||||
capabilities: {},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -27,12 +27,6 @@ export default Ember.Route.extend({
|
|||
}
|
||||
},
|
||||
|
||||
capabilities(secret) {
|
||||
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
||||
const path = backend + '/' + secret;
|
||||
return this.store.findRecord('capabilities', path);
|
||||
},
|
||||
|
||||
getModelType(backend, tab) {
|
||||
const types = {
|
||||
transit: 'transit-key',
|
||||
|
@ -49,29 +43,26 @@ export default Ember.Route.extend({
|
|||
const secret = params.secret ? params.secret : '';
|
||||
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
||||
const backends = this.modelFor('vault.cluster.secrets').mapBy('id');
|
||||
return Ember.RSVP.hash({
|
||||
secrets: this.store
|
||||
.lazyPaginatedQuery(this.getModelType(backend, params.tab), {
|
||||
id: secret,
|
||||
backend,
|
||||
responsePath: 'data.keys',
|
||||
page: params.page,
|
||||
pageFilter: params.pageFilter,
|
||||
size: 100,
|
||||
})
|
||||
.then(model => {
|
||||
this.set('has404', false);
|
||||
return model;
|
||||
})
|
||||
.catch(err => {
|
||||
if (backends.includes(backend) && err.httpStatus === 404 && secret === '') {
|
||||
return [];
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}),
|
||||
capabilities: this.capabilities(secret),
|
||||
});
|
||||
return this.store
|
||||
.lazyPaginatedQuery(this.getModelType(backend, params.tab), {
|
||||
id: secret,
|
||||
backend,
|
||||
responsePath: 'data.keys',
|
||||
page: params.page,
|
||||
pageFilter: params.pageFilter,
|
||||
size: 100,
|
||||
})
|
||||
.then(model => {
|
||||
this.set('has404', false);
|
||||
return model;
|
||||
})
|
||||
.catch(err => {
|
||||
if (backends.includes(backend) && err.httpStatus === 404 && secret === '') {
|
||||
return [];
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
afterModel(model) {
|
||||
|
@ -107,8 +98,7 @@ export default Ember.Route.extend({
|
|||
const has404 = this.get('has404');
|
||||
controller.set('hasModel', true);
|
||||
controller.setProperties({
|
||||
model: model.secrets,
|
||||
capabilities: model.capabilities,
|
||||
model,
|
||||
baseKey: { id: secret },
|
||||
has404,
|
||||
backend,
|
||||
|
@ -125,7 +115,7 @@ export default Ember.Route.extend({
|
|||
}
|
||||
controller.setProperties({
|
||||
filter: filter || '',
|
||||
page: model.secrets.get('meta.currentPage') || 1,
|
||||
page: model.get('meta.currentPage') || 1,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
@ -5,11 +5,16 @@ import UnloadModelRoute from 'vault/mixins/unload-model-route';
|
|||
export default Ember.Route.extend(UnloadModelRoute, {
|
||||
capabilities(secret) {
|
||||
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
||||
let backendModel = this.store.peekRecord('secret-engine', backend);
|
||||
let backendType = backendModel.get('type');
|
||||
let version = backendModel.get('options.version');
|
||||
let path;
|
||||
if (backend === 'transit') {
|
||||
if (backendType === 'transit') {
|
||||
path = backend + '/keys/' + secret;
|
||||
} else if (backend === 'ssh' || backend === 'aws') {
|
||||
} else if (backendType === 'ssh' || backendType === 'aws') {
|
||||
path = backend + '/roles/' + secret;
|
||||
} else if (version && version === 2) {
|
||||
path = backend + '/data/' + secret;
|
||||
} else {
|
||||
path = backend + '/' + secret;
|
||||
}
|
||||
|
|
|
@ -52,20 +52,18 @@
|
|||
</div>
|
||||
<div class="field is-grouped-split box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
{{#if capabilities.canCreate}}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={{buttonDisabled}}
|
||||
class="button is-primary"
|
||||
data-test-role-aws-create=true
|
||||
>
|
||||
{{#if (eq mode 'create')}}
|
||||
Create role
|
||||
{{else if (eq mode 'edit')}}
|
||||
Save
|
||||
{{/if}}
|
||||
</button>
|
||||
{{/if}}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={{buttonDisabled}}
|
||||
class="button is-primary"
|
||||
data-test-role-aws-create=true
|
||||
>
|
||||
{{#if (eq mode 'create')}}
|
||||
Create role
|
||||
{{else if (eq mode 'edit')}}
|
||||
Save
|
||||
{{/if}}
|
||||
</button>
|
||||
{{#secret-link
|
||||
mode=(if (eq mode "create") "list" "show")
|
||||
class="button"
|
||||
|
|
|
@ -5,20 +5,18 @@
|
|||
</div>
|
||||
<div class="field is-grouped-split box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
{{#if capabilities.canCreate}}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={{buttonDisabled}}
|
||||
class="button is-primary"
|
||||
data-test-role-create=true
|
||||
>
|
||||
{{#if (eq mode 'create')}}
|
||||
Create role
|
||||
{{else if (eq mode 'edit')}}
|
||||
Save
|
||||
{{/if}}
|
||||
</button>
|
||||
{{/if}}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={{buttonDisabled}}
|
||||
class="button is-primary"
|
||||
data-test-role-create=true
|
||||
>
|
||||
{{#if (eq mode 'create')}}
|
||||
Create role
|
||||
{{else if (eq mode 'edit')}}
|
||||
Save
|
||||
{{/if}}
|
||||
</button>
|
||||
{{#secret-link
|
||||
mode=(if (eq mode "create") "list" "show")
|
||||
class="button"
|
||||
|
|
|
@ -25,20 +25,18 @@
|
|||
</div>
|
||||
<div class="field is-grouped-split box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
{{#if capabilities.canCreate}}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={{buttonDisabled}}
|
||||
class="button is-primary"
|
||||
data-test-role-ssh-create=true
|
||||
>
|
||||
{{#if (eq mode 'create')}}
|
||||
Create role
|
||||
{{else if (eq mode 'edit')}}
|
||||
Save
|
||||
{{/if}}
|
||||
</button>
|
||||
{{/if}}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={{buttonDisabled}}
|
||||
class="button is-primary"
|
||||
data-test-role-ssh-create=true
|
||||
>
|
||||
{{#if (eq mode 'create')}}
|
||||
Create role
|
||||
{{else if (eq mode 'edit')}}
|
||||
Save
|
||||
{{/if}}
|
||||
</button>
|
||||
{{#secret-link
|
||||
mode=(if (eq mode "create") "list" "show")
|
||||
class="button"
|
||||
|
|
|
@ -34,16 +34,14 @@
|
|||
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
{{#if capabilities.canCreate}}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={{buttonDisabled}}
|
||||
class="button is-primary"
|
||||
data-test-secret-save=true
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={{buttonDisabled}}
|
||||
class="button is-primary"
|
||||
data-test-secret-save=true
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
{{/if}}
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
{{#secret-link
|
||||
|
|
|
@ -80,11 +80,11 @@
|
|||
<div class="field">
|
||||
<div class="b-checkbox">
|
||||
<input type="checkbox"
|
||||
data-test-transit-key-convergent-encryption
|
||||
id="key-convergent"
|
||||
class="styled"
|
||||
checked={{key.convergentEncryption}}
|
||||
onchange={{action "convergentEncryptionChange" value="target.checked"}}
|
||||
data-test-transit-key-convergent-encryption=true
|
||||
/>
|
||||
<label for="key-convergent" class="is-label">
|
||||
Enable convergent encryption
|
||||
|
@ -94,18 +94,16 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
{{#if capabilities.canCreate}}
|
||||
<div class="control">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={{requestInFlight}}
|
||||
class="button is-primary {{if requestInFlight 'is-loading'}}"
|
||||
data-test-transit-key-create=true
|
||||
>
|
||||
Create encryption key
|
||||
</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="control">
|
||||
<button
|
||||
data-test-transit-key-create
|
||||
type="submit"
|
||||
disabled={{requestInFlight}}
|
||||
class="button is-primary {{if requestInFlight 'is-loading'}}"
|
||||
>
|
||||
Create encryption key
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
{{#secret-link
|
||||
mode="list"
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</h1>
|
||||
</div>
|
||||
<div class="level-right field is-grouped">
|
||||
{{#if (and (not-eq tab "certs") capabilities.canCreate)}}
|
||||
{{#if (not-eq tab "certs")}}
|
||||
<div class="control">
|
||||
{{#secret-link
|
||||
mode="create"
|
||||
|
@ -114,8 +114,7 @@
|
|||
{{else}}
|
||||
<div class="box is-sideless">
|
||||
{{#if filterFocused}}
|
||||
There are no {{pluralize options.item}} matching <code>{{filter}}</code>
|
||||
{{#if capabilities.canCreate}}, press <kbd>ENTER</kbd> to add one{{/if}}.
|
||||
There are no {{pluralize options.item}} matching <code>{{filter}}</code>, press <kbd>ENTER</kbd> to add one.
|
||||
{{else}}
|
||||
There are no {{pluralize options.item}} matching <code>{{filter}}</code>.
|
||||
{{/if}}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<div class="level-left">
|
||||
<div>
|
||||
<a data-test-secret-path href={{href-to 'vault.cluster.secrets.backend.list-root' backend.id}} class="has-text-black has-text-weight-semibold">
|
||||
{{i-con glyph="folder" size=14 class="has-text-grey-light"}} {{backend.path}}
|
||||
{{i-con glyph="folder" size=14 class="has-text-grey-light"}}{{backend.path}}
|
||||
</a>
|
||||
<br />
|
||||
<span class="tag">
|
||||
|
|
|
@ -56,10 +56,10 @@
|
|||
<div class="control is-expanded">
|
||||
<div class="select is-fullwidth">
|
||||
<select
|
||||
data-test-secret-backend-version
|
||||
id="backend-type"
|
||||
value={{selectedType}}
|
||||
onchange={{action (mut version) value="target.value"}}
|
||||
data-test-secret-backend-type=true
|
||||
>
|
||||
{{#each (array 1 2) as |versionOption|}}
|
||||
<option selected={{eq version versionOption}} value={{versionOption}}>
|
||||
|
|
|
@ -5,8 +5,10 @@ if(process.argv[2]){
|
|||
process.exit(0);
|
||||
}
|
||||
|
||||
process.env.TERM = 'dumb';
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var readline = require('readline')
|
||||
var spawn = require('child_process').spawn;
|
||||
var vault = spawn(
|
||||
'vault',
|
||||
|
@ -21,21 +23,15 @@ var vault = spawn(
|
|||
]
|
||||
);
|
||||
|
||||
// https://github.com/chalk/ansi-regex/blob/master/index.js
|
||||
var ansiPattern = [
|
||||
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)',
|
||||
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))'
|
||||
].join('|');
|
||||
|
||||
var ANSI_REGEX = new RegExp(ansiPattern, 'g');
|
||||
|
||||
var output = '';
|
||||
var unseal, root;
|
||||
vault.stdout.on('data', function(data) {
|
||||
var stringData = data.toString().replace(ANSI_REGEX, '');
|
||||
output = output + stringData;
|
||||
console.log(stringData);
|
||||
|
||||
readline.createInterface({
|
||||
input : vault.stdout,
|
||||
terminal : false
|
||||
}).on('line', function(line) {
|
||||
output = output + line;
|
||||
console.log(line);
|
||||
var unsealMatch = output.match(/Unseal Key\: (.+)$/m);
|
||||
if (unsealMatch && !unseal) { unseal = unsealMatch[1] };
|
||||
var rootMatch = output.match(/Root Token\: (.+)$/m);
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { test } from 'qunit';
|
||||
import { test, skip } from 'qunit';
|
||||
import moduleForAcceptance from 'vault/tests/helpers/module-for-acceptance';
|
||||
import secretList from 'vault/tests/pages/secrets/backend/list';
|
||||
import secretEdit from 'vault/tests/pages/secrets/backend/kv/edit-secret';
|
||||
import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
|
||||
import Ember from 'ember';
|
||||
|
||||
let adapterException;
|
||||
|
@ -9,7 +12,11 @@ moduleForAcceptance('Acceptance | leases', {
|
|||
beforeEach() {
|
||||
adapterException = Ember.Test.adapter.exception;
|
||||
Ember.Test.adapter.exception = () => null;
|
||||
return authLogin();
|
||||
|
||||
authLogin();
|
||||
this.enginePath = `kv-for-lease-${new Date().getTime()}`;
|
||||
// need a version 1 mount for leased secrets here
|
||||
return mountSecrets.visit().path(this.enginePath).type('kv').version(1).submit();
|
||||
},
|
||||
afterEach() {
|
||||
Ember.Test.adapter.exception = adapterException;
|
||||
|
@ -18,43 +25,31 @@ moduleForAcceptance('Acceptance | leases', {
|
|||
});
|
||||
|
||||
const createSecret = (context, isRenewable) => {
|
||||
const now = new Date().getTime();
|
||||
const secretContents = { secret: 'foo' };
|
||||
context.name = `secret-${new Date().getTime()}`;
|
||||
secretList.visitRoot({ backend: context.enginePath });
|
||||
secretList.create();
|
||||
if (isRenewable) {
|
||||
secretContents.ttl = '30h';
|
||||
secretEdit.createSecret(context.name, 'ttl', '30h');
|
||||
} else {
|
||||
secretEdit.createSecret(context.name, 'foo', 'bar');
|
||||
}
|
||||
context.secret = {
|
||||
name: isRenewable ? `renew-secret-${now}` : `secret-${now}`,
|
||||
text: JSON.stringify(secretContents),
|
||||
};
|
||||
//create a secret so we have a lease (server is running in -dev-leased-kv mode)
|
||||
visit('/vault/secrets/secret/list');
|
||||
click('[data-test-secret-create]');
|
||||
fillIn('[data-test-secret-path]', context.secret.name);
|
||||
andThen(() => {
|
||||
const codeMirror = find('.CodeMirror');
|
||||
// UI keeps state so once we flip to json, we don't need to again
|
||||
if (!codeMirror.length) {
|
||||
click('[data-test-secret-json-toggle]');
|
||||
}
|
||||
});
|
||||
andThen(() => {
|
||||
find('.CodeMirror').get(0).CodeMirror.setValue(context.secret.text);
|
||||
});
|
||||
click('[data-test-secret-save]');
|
||||
};
|
||||
|
||||
const navToDetail = secret => {
|
||||
const navToDetail = context => {
|
||||
visit('/vault/access/leases/');
|
||||
click('[data-test-lease-link="secret/"]');
|
||||
click(`[data-test-lease-link="secret/${secret.name}/"]`);
|
||||
// all the
|
||||
click(`[data-test-lease-link="${context.enginePath}/"]`);
|
||||
// way down
|
||||
click(`[data-test-lease-link="${context.enginePath}/data/"]`);
|
||||
// the tree
|
||||
click(`[data-test-lease-link="${context.enginePath}/data/${context.name}/"]`);
|
||||
click(`[data-test-lease-link]:eq(0)`);
|
||||
};
|
||||
|
||||
test('it renders the show page', function(assert) {
|
||||
createSecret(this);
|
||||
navToDetail(this.secret);
|
||||
andThen(() => {
|
||||
navToDetail(this);
|
||||
return andThen(() => {
|
||||
assert.equal(
|
||||
currentRouteName(),
|
||||
'vault.cluster.access.leases.show',
|
||||
|
@ -68,9 +63,10 @@ test('it renders the show page', function(assert) {
|
|||
});
|
||||
});
|
||||
|
||||
test('it renders the show page with a picker', function(assert) {
|
||||
// skip for now until we find an easy way to generate a renewable lease
|
||||
skip('it renders the show page with a picker', function(assert) {
|
||||
createSecret(this, true);
|
||||
navToDetail(this.secret);
|
||||
navToDetail(this);
|
||||
andThen(() => {
|
||||
assert.equal(
|
||||
currentRouteName(),
|
||||
|
@ -83,7 +79,7 @@ test('it renders the show page with a picker', function(assert) {
|
|||
|
||||
test('it removes leases upon revocation', function(assert) {
|
||||
createSecret(this);
|
||||
navToDetail(this.secret);
|
||||
navToDetail(this);
|
||||
click('[data-test-lease-revoke] button');
|
||||
click('[data-test-confirm-button]');
|
||||
andThen(() => {
|
||||
|
@ -93,10 +89,11 @@ test('it removes leases upon revocation', function(assert) {
|
|||
'it navigates back to the leases root on revocation'
|
||||
);
|
||||
});
|
||||
click('[data-test-lease-link="secret/"]');
|
||||
click(`[data-test-lease-link="${this.enginePath}/"]`);
|
||||
click('[data-test-lease-link="data/"]');
|
||||
andThen(() => {
|
||||
assert.equal(
|
||||
find(`[data-test-lease-link="secret/${this.secret.name}/"]`).length,
|
||||
find(`[data-test-lease-link="${this.enginePath}/data/${this.name}/"]`).length,
|
||||
0,
|
||||
'link to the lease was removed with revocation'
|
||||
);
|
||||
|
@ -105,7 +102,7 @@ test('it removes leases upon revocation', function(assert) {
|
|||
|
||||
test('it removes branches when a prefix is revoked', function(assert) {
|
||||
createSecret(this);
|
||||
visit('/vault/access/leases/list/secret/');
|
||||
visit(`/vault/access/leases/list/${this.enginePath}`);
|
||||
click('[data-test-lease-revoke-prefix] button');
|
||||
click('[data-test-confirm-button]');
|
||||
andThen(() => {
|
||||
|
@ -115,7 +112,7 @@ test('it removes branches when a prefix is revoked', function(assert) {
|
|||
'it navigates back to the leases root on revocation'
|
||||
);
|
||||
assert.equal(
|
||||
find('[data-test-lease-link="secret/"]').length,
|
||||
find(`[data-test-lease-link="${this.enginePath}/"]`).length,
|
||||
0,
|
||||
'link to the prefix was removed with revocation'
|
||||
);
|
||||
|
|
|
@ -4,14 +4,26 @@ import editPage from 'vault/tests/pages/secrets/backend/kv/edit-secret';
|
|||
import showPage from 'vault/tests/pages/secrets/backend/kv/show';
|
||||
import listPage from 'vault/tests/pages/secrets/backend/list';
|
||||
|
||||
import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
|
||||
import Pretender from 'pretender';
|
||||
|
||||
moduleForAcceptance('Acceptance | secrets/secret/create', {
|
||||
beforeEach() {
|
||||
this.server = new Pretender(function() {
|
||||
this.post('/v1/**', this.passthrough);
|
||||
this.put('/v1/**', this.passthrough);
|
||||
this.get('/v1/**', this.passthrough);
|
||||
this.delete('/v1/**', this.passthrough);
|
||||
});
|
||||
return authLogin();
|
||||
},
|
||||
afterEach() {
|
||||
this.server.shutdown();
|
||||
},
|
||||
});
|
||||
|
||||
test('it creates a secret and redirects', function(assert) {
|
||||
const path = `kv-${new Date().getTime()}`;
|
||||
const path = `kv-path-${new Date().getTime()}`;
|
||||
listPage.visitRoot({ backend: 'secret' });
|
||||
andThen(() => {
|
||||
assert.equal(currentRouteName(), 'vault.cluster.secrets.backend.list-root', 'navigates to the list page');
|
||||
|
@ -20,7 +32,30 @@ test('it creates a secret and redirects', function(assert) {
|
|||
listPage.create();
|
||||
editPage.createSecret(path, 'foo', 'bar');
|
||||
andThen(() => {
|
||||
let capabilitiesReq = this.server.passthroughRequests.findBy('url', '/v1/sys/capabilities-self');
|
||||
assert.equal(
|
||||
JSON.parse(capabilitiesReq.requestBody).path,
|
||||
`secret/data/${path}`,
|
||||
'calls capabilites with the correct path'
|
||||
);
|
||||
assert.equal(currentRouteName(), 'vault.cluster.secrets.backend.show', 'redirects to the show page');
|
||||
assert.ok(showPage.editIsPresent, 'shows the edit button');
|
||||
});
|
||||
});
|
||||
|
||||
test('version 1 performs the correct capabilities lookup', function(assert) {
|
||||
let enginePath = `kv-${new Date().getTime()}`;
|
||||
let secretPath = 'foo/bar';
|
||||
mountSecrets.visit().path(enginePath).type('kv').version(1).submit();
|
||||
|
||||
listPage.create();
|
||||
editPage.createSecret(secretPath, 'foo', 'bar');
|
||||
andThen(() => {
|
||||
let capabilitiesReq = this.server.passthroughRequests.findBy('url', '/v1/sys/capabilities-self');
|
||||
assert.equal(
|
||||
JSON.parse(capabilitiesReq.requestBody).path,
|
||||
`${enginePath}/${secretPath}`,
|
||||
'calls capabilites with the correct path'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@ export default create({
|
|||
path: fillable('[data-test-secret-backend-path]'),
|
||||
submit: clickable('[data-test-secret-backend-submit]'),
|
||||
toggleOptions: clickable('[data-test-secret-backend-options]'),
|
||||
version: fillable('[data-test-secret-backend-version]'),
|
||||
maxTTLVal: fillable('[data-test-ttl-value]', { scope: '[data-test-secret-backend-max-ttl]' }),
|
||||
maxTTLUnit: fillable('[data-test-ttl-unit]', { scope: '[data-test-secret-backend-max-ttl]' }),
|
||||
defaultTTLVal: fillable('[data-test-ttl-value]', { scope: '[data-test-secret-backend-default-ttl]' }),
|
||||
|
|
|
@ -802,6 +802,10 @@ func (c *Core) checkToken(ctx context.Context, req *logical.Request, unauth bool
|
|||
}
|
||||
}
|
||||
|
||||
if entity != nil && entity.Disabled {
|
||||
return nil, te, logical.ErrEntityDisabled
|
||||
}
|
||||
|
||||
// Check if this is a root protected path
|
||||
rootPath := c.router.RootPath(req.Path)
|
||||
|
||||
|
@ -1319,20 +1323,21 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
|
|||
return retErr
|
||||
}
|
||||
|
||||
// Since there is no token store in standby nodes, sealing cannot be done.
|
||||
// Ideally, the request has to be forwarded to leader node for validation
|
||||
// and the operation should be performed. But for now, just returning with
|
||||
// an error and recommending a vault restart, which essentially does the
|
||||
// same thing.
|
||||
if c.standby {
|
||||
c.logger.Error("vault cannot seal when in standby mode; please restart instead")
|
||||
retErr = multierror.Append(retErr, errors.New("vault cannot seal when in standby mode; please restart instead"))
|
||||
c.stateLock.RUnlock()
|
||||
return retErr
|
||||
}
|
||||
|
||||
// Validate the token is a root token
|
||||
acl, te, entity, err := c.fetchACLTokenEntryAndEntity(req.ClientToken)
|
||||
if err != nil {
|
||||
// Since there is no token store in standby nodes, sealing cannot
|
||||
// be done. Ideally, the request has to be forwarded to leader node
|
||||
// for validation and the operation should be performed. But for now,
|
||||
// just returning with an error and recommending a vault restart, which
|
||||
// essentially does the same thing.
|
||||
if c.standby {
|
||||
c.logger.Error("vault cannot seal when in standby mode; please restart instead")
|
||||
retErr = multierror.Append(retErr, errors.New("vault cannot seal when in standby mode; please restart instead"))
|
||||
c.stateLock.RUnlock()
|
||||
return retErr
|
||||
}
|
||||
retErr = multierror.Append(retErr, err)
|
||||
c.stateLock.RUnlock()
|
||||
return retErr
|
||||
|
@ -1341,10 +1346,12 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
|
|||
// Audit-log the request before going any further
|
||||
auth := &logical.Auth{
|
||||
ClientToken: req.ClientToken,
|
||||
Policies: te.Policies,
|
||||
Metadata: te.Meta,
|
||||
DisplayName: te.DisplayName,
|
||||
EntityID: te.EntityID,
|
||||
}
|
||||
if te != nil {
|
||||
auth.Policies = te.Policies
|
||||
auth.Metadata = te.Meta
|
||||
auth.DisplayName = te.DisplayName
|
||||
auth.EntityID = te.EntityID
|
||||
}
|
||||
|
||||
logInput := &audit.LogInput{
|
||||
|
@ -1358,6 +1365,12 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
|
|||
return retErr
|
||||
}
|
||||
|
||||
if entity != nil && entity.Disabled {
|
||||
retErr = multierror.Append(retErr, logical.ErrEntityDisabled)
|
||||
c.stateLock.RUnlock()
|
||||
return retErr
|
||||
}
|
||||
|
||||
// Attempt to use the token (decrement num_uses)
|
||||
// On error bail out; if the token has been revoked, bail out too
|
||||
if te != nil {
|
||||
|
@ -1450,10 +1463,12 @@ func (c *Core) StepDown(req *logical.Request) (retErr error) {
|
|||
// Audit-log the request before going any further
|
||||
auth := &logical.Auth{
|
||||
ClientToken: req.ClientToken,
|
||||
Policies: te.Policies,
|
||||
Metadata: te.Meta,
|
||||
DisplayName: te.DisplayName,
|
||||
EntityID: te.EntityID,
|
||||
}
|
||||
if te != nil {
|
||||
auth.Policies = te.Policies
|
||||
auth.Metadata = te.Meta
|
||||
auth.DisplayName = te.DisplayName
|
||||
auth.EntityID = te.EntityID
|
||||
}
|
||||
|
||||
logInput := &audit.LogInput{
|
||||
|
@ -1466,6 +1481,12 @@ func (c *Core) StepDown(req *logical.Request) (retErr error) {
|
|||
return retErr
|
||||
}
|
||||
|
||||
if entity != nil && entity.Disabled {
|
||||
retErr = multierror.Append(retErr, logical.ErrEntityDisabled)
|
||||
c.stateLock.RUnlock()
|
||||
return retErr
|
||||
}
|
||||
|
||||
// Attempt to use the token (decrement num_uses)
|
||||
if te != nil {
|
||||
te, err = c.tokenStore.UseToken(ctx, te)
|
||||
|
|
|
@ -434,7 +434,7 @@ var aliasHelp = map[string][2]string{
|
|||
"",
|
||||
},
|
||||
"alias-id-list": {
|
||||
"List all the entity IDs.",
|
||||
"List all the alias IDs.",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -45,6 +45,10 @@ vault <command> <path> metadata=key1=value1 metadata=key2=value2
|
|||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "Policies to be tied to the entity.",
|
||||
},
|
||||
"disabled": {
|
||||
Type: framework.TypeBool,
|
||||
Description: "If set true, tokens tied to this identity will not be able to be used (but will not be revoked).",
|
||||
},
|
||||
},
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: i.pathEntityRegister(),
|
||||
|
@ -76,6 +80,10 @@ vault <command> <path> metadata=key1=value1 metadata=key2=value2
|
|||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "Policies to be tied to the entity.",
|
||||
},
|
||||
"disabled": {
|
||||
Type: framework.TypeBool,
|
||||
Description: "If set true, tokens tied to this identity will not be able to be used (but will not be revoked).",
|
||||
},
|
||||
},
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: i.pathEntityIDUpdate(),
|
||||
|
@ -354,6 +362,11 @@ func (i *IdentityStore) handleEntityUpdateCommon(req *logical.Request, d *framew
|
|||
entity.Policies = entityPoliciesRaw.([]string)
|
||||
}
|
||||
|
||||
disabledRaw, ok := d.GetOk("disabled")
|
||||
if ok {
|
||||
entity.Disabled = disabledRaw.(bool)
|
||||
}
|
||||
|
||||
// Get the name
|
||||
entityName := d.Get("name").(string)
|
||||
if entityName != "" {
|
||||
|
@ -434,6 +447,7 @@ func (i *IdentityStore) handleEntityReadCommon(entity *identity.Entity) (*logica
|
|||
respData["metadata"] = entity.Metadata
|
||||
respData["merged_entity_ids"] = entity.MergedEntityIDs
|
||||
respData["policies"] = entity.Policies
|
||||
respData["disabled"] = entity.Disabled
|
||||
|
||||
// Convert protobuf timestamp into RFC3339 format
|
||||
respData["creation_time"] = ptypes.TimestampString(entity.CreationTime)
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
package vault_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/builtin/credential/approle"
|
||||
vaulthttp "github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
func TestIdentityStore_EntityDisabled(t *testing.T) {
|
||||
// Use a TestCluster and the approle backend to get a token and entity for testing
|
||||
coreConfig := &vault.CoreConfig{
|
||||
CredentialBackends: map[string]logical.Factory{
|
||||
"approle": approle.Factory,
|
||||
},
|
||||
}
|
||||
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
})
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
||||
core := cluster.Cores[0].Core
|
||||
vault.TestWaitActive(t, core)
|
||||
client := cluster.Cores[0].Client
|
||||
|
||||
// Mount the auth backend
|
||||
err := client.Sys().EnableAuthWithOptions("approle", &api.EnableAuthOptions{
|
||||
Type: "approle",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Tune the mount
|
||||
err = client.Sys().TuneMount("auth/approle", api.MountConfigInput{
|
||||
DefaultLeaseTTL: "5m",
|
||||
MaxLeaseTTL: "5m",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create role
|
||||
resp, err := client.Logical().Write("auth/approle/role/role-period", map[string]interface{}{
|
||||
"period": "5m",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Get role_id
|
||||
resp, err = client.Logical().Read("auth/approle/role/role-period/role-id")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected a response for fetching the role-id")
|
||||
}
|
||||
roleID := resp.Data["role_id"]
|
||||
|
||||
// Get secret_id
|
||||
resp, err = client.Logical().Write("auth/approle/role/role-period/secret-id", map[string]interface{}{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected a response for fetching the secret-id")
|
||||
}
|
||||
secretID := resp.Data["secret_id"]
|
||||
|
||||
// Login
|
||||
resp, err = client.Logical().Write("auth/approle/login", map[string]interface{}{
|
||||
"role_id": roleID,
|
||||
"secret_id": secretID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected a response for login")
|
||||
}
|
||||
if resp.Auth == nil {
|
||||
t.Fatal("expected auth object from response")
|
||||
}
|
||||
if resp.Auth.ClientToken == "" {
|
||||
t.Fatal("expected a client token")
|
||||
}
|
||||
|
||||
roleToken := resp.Auth.ClientToken
|
||||
|
||||
client.SetToken(roleToken)
|
||||
resp, err = client.Auth().Token().LookupSelf()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected a response for token lookup")
|
||||
}
|
||||
entityIDRaw, ok := resp.Data["entity_id"]
|
||||
if !ok {
|
||||
t.Fatal("expected an entity ID")
|
||||
}
|
||||
entityID, ok := entityIDRaw.(string)
|
||||
if !ok {
|
||||
t.Fatal("entity_id not a string")
|
||||
}
|
||||
|
||||
client.SetToken(cluster.RootToken)
|
||||
resp, err = client.Logical().Write("identity/entity/id/"+entityID, map[string]interface{}{
|
||||
"disabled": true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// This call should now fail
|
||||
client.SetToken(roleToken)
|
||||
resp, err = client.Auth().Token().LookupSelf()
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got %#v", *resp)
|
||||
}
|
||||
if !strings.Contains(err.Error(), logical.ErrEntityDisabled.Error()) {
|
||||
t.Fatalf("expected to see entity disabled error, got %v", err)
|
||||
}
|
||||
|
||||
// Attempting to get a new token should also now fail
|
||||
client.SetToken("")
|
||||
resp, err = client.Logical().Write("auth/approle/login", map[string]interface{}{
|
||||
"role_id": roleID,
|
||||
"secret_id": secretID,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got %#v", *resp)
|
||||
}
|
||||
if !strings.Contains(err.Error(), logical.ErrEntityDisabled.Error()) {
|
||||
t.Fatalf("expected to see entity disabled error, got %v", err)
|
||||
}
|
||||
|
||||
client.SetToken(cluster.RootToken)
|
||||
resp, err = client.Logical().Write("identity/entity/id/"+entityID, map[string]interface{}{
|
||||
"disabled": false,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
client.SetToken(roleToken)
|
||||
resp, err = client.Auth().Token().LookupSelf()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Getting a new token should now work again too
|
||||
client.SetToken("")
|
||||
resp, err = client.Logical().Write("auth/approle/login", map[string]interface{}{
|
||||
"role_id": roleID,
|
||||
"secret_id": secretID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected a response for login")
|
||||
}
|
||||
if resp.Auth == nil {
|
||||
t.Fatal("expected auth object from response")
|
||||
}
|
||||
if resp.Auth.ClientToken == "" {
|
||||
t.Fatal("expected a client token")
|
||||
}
|
||||
}
|
|
@ -68,7 +68,7 @@ func groupAliasPaths(i *IdentityStore) []*framework.Path {
|
|||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(groupAliasHelp["group-alias-by-id"][0]),
|
||||
HelpDescription: strings.TrimSpace(groupHelp["group-alias-by-id"][1]),
|
||||
HelpDescription: strings.TrimSpace(groupAliasHelp["group-alias-by-id"][1]),
|
||||
},
|
||||
{
|
||||
Pattern: "group-alias/id/?$",
|
||||
|
@ -76,8 +76,8 @@ func groupAliasPaths(i *IdentityStore) []*framework.Path {
|
|||
logical.ListOperation: i.pathGroupAliasIDList(),
|
||||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(entityHelp["group-alias-id-list"][0]),
|
||||
HelpDescription: strings.TrimSpace(entityHelp["group-alias-id-list"][1]),
|
||||
HelpSynopsis: strings.TrimSpace(groupAliasHelp["group-alias-id-list"][0]),
|
||||
HelpDescription: strings.TrimSpace(groupAliasHelp["group-alias-id-list"][1]),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -285,7 +285,7 @@ var groupAliasHelp = map[string][2]string{
|
|||
"",
|
||||
},
|
||||
"group-alias-id-list": {
|
||||
"List all the entity IDs.",
|
||||
"List all the group alias IDs.",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -115,8 +115,8 @@ vault <command> <path> metadata=key1=value1 metadata=key2=value2
|
|||
logical.ListOperation: i.pathGroupIDList(),
|
||||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(entityHelp["group-id-list"][0]),
|
||||
HelpDescription: strings.TrimSpace(entityHelp["group-id-list"][1]),
|
||||
HelpSynopsis: strings.TrimSpace(groupHelp["group-id-list"][0]),
|
||||
HelpDescription: strings.TrimSpace(groupHelp["group-id-list"][1]),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
package vault_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
vaulthttp "github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
|
||||
credLdap "github.com/hashicorp/vault/builtin/credential/ldap"
|
||||
)
|
||||
|
||||
// Testing the fix for GH-4351
|
||||
func TestIdentityStore_ExternalGroupMembershipsAcrossMounts(t *testing.T) {
|
||||
coreConfig := &vault.CoreConfig{
|
||||
CredentialBackends: map[string]logical.Factory{
|
||||
"ldap": credLdap.Factory,
|
||||
},
|
||||
}
|
||||
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
})
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
||||
core := cluster.Cores[0].Core
|
||||
vault.TestWaitActive(t, core)
|
||||
client := cluster.Cores[0].Client
|
||||
|
||||
// Enable the first LDAP auth
|
||||
err := client.Sys().EnableAuthWithOptions("ldap", &api.EnableAuthOptions{
|
||||
Type: "ldap",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Extract out the mount accessor for LDAP auth
|
||||
auths, err := client.Sys().ListAuth()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ldapMountAccessor1 := auths["ldap/"].Accessor
|
||||
|
||||
// Configure LDAP auth
|
||||
_, err = client.Logical().Write("auth/ldap/config", map[string]interface{}{
|
||||
"url": "ldap://ldap.forumsys.com",
|
||||
"userattr": "uid",
|
||||
"userdn": "dc=example,dc=com",
|
||||
"groupdn": "dc=example,dc=com",
|
||||
"binddn": "cn=read-only-admin,dc=example,dc=com",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a group in LDAP auth
|
||||
_, err = client.Logical().Write("auth/ldap/groups/testgroup1", map[string]interface{}{
|
||||
"policies": "testgroup1-policy",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Tie the group to a user
|
||||
_, err = client.Logical().Write("auth/ldap/users/tesla", map[string]interface{}{
|
||||
"policies": "default",
|
||||
"groups": "testgroup1",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create an external group
|
||||
secret, err := client.Logical().Write("identity/group", map[string]interface{}{
|
||||
"type": "external",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ldapExtGroupID1 := secret.Data["id"].(string)
|
||||
|
||||
// Associate a group from LDAP auth as a group-alias in the external group
|
||||
_, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
|
||||
"name": "testgroup1",
|
||||
"mount_accessor": ldapMountAccessor1,
|
||||
"canonical_id": ldapExtGroupID1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Login using LDAP
|
||||
secret, err = client.Logical().Write("auth/ldap/login/tesla", map[string]interface{}{
|
||||
"password": "password",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ldapClientToken := secret.Auth.ClientToken
|
||||
|
||||
//
|
||||
// By now, the entity ID of the token should be automatically added as a
|
||||
// member in the external group.
|
||||
//
|
||||
|
||||
// Extract the entity ID of the token
|
||||
secret, err = client.Logical().Read("auth/token/lookup/" + ldapClientToken)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
entityID := secret.Data["entity_id"].(string)
|
||||
|
||||
// Enable another LDAP auth mount
|
||||
err = client.Sys().EnableAuthWithOptions("ldap2", &api.EnableAuthOptions{
|
||||
Type: "ldap",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Extract the mount accessor
|
||||
auths, err = client.Sys().ListAuth()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ldapMountAccessor2 := auths["ldap2/"].Accessor
|
||||
|
||||
// Create an entity-alias asserting that the user "tesla" from the first
|
||||
// and second LDAP mounts as the same.
|
||||
_, err = client.Logical().Write("identity/entity-alias", map[string]interface{}{
|
||||
"name": "tesla",
|
||||
"mount_accessor": ldapMountAccessor2,
|
||||
"canonical_id": entityID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Configure second LDAP auth
|
||||
_, err = client.Logical().Write("auth/ldap2/config", map[string]interface{}{
|
||||
"url": "ldap://ldap.forumsys.com",
|
||||
"userattr": "uid",
|
||||
"userdn": "dc=example,dc=com",
|
||||
"groupdn": "dc=example,dc=com",
|
||||
"binddn": "cn=read-only-admin,dc=example,dc=com",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a group in second LDAP auth
|
||||
_, err = client.Logical().Write("auth/ldap2/groups/testgroup2", map[string]interface{}{
|
||||
"policies": "testgroup2-policy",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a user in second LDAP auth
|
||||
_, err = client.Logical().Write("auth/ldap2/users/tesla", map[string]interface{}{
|
||||
"policies": "default",
|
||||
"groups": "testgroup2",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create another external group
|
||||
secret, err = client.Logical().Write("identity/group", map[string]interface{}{
|
||||
"type": "external",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ldapExtGroupID2 := secret.Data["id"].(string)
|
||||
|
||||
// Create a group-alias tying the external group to "testgroup2" group in second LDAP
|
||||
_, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
|
||||
"name": "testgroup2",
|
||||
"mount_accessor": ldapMountAccessor2,
|
||||
"canonical_id": ldapExtGroupID2,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Login using second LDAP
|
||||
_, err = client.Logical().Write("auth/ldap2/login/tesla", map[string]interface{}{
|
||||
"password": "password",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//
|
||||
// By now the same entity ID of the token from first LDAP should have been
|
||||
// added as a member of the second external group.
|
||||
//
|
||||
|
||||
// Check that entityID is present in both the external groups
|
||||
secret, err = client.Logical().Read("identity/group/id/" + ldapExtGroupID1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
extGroup1Entities := secret.Data["member_entity_ids"].([]interface{})
|
||||
|
||||
found := false
|
||||
for _, item := range extGroup1Entities {
|
||||
if item.(string) == entityID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("missing entity ID %q first external group with ID %q", entityID, ldapExtGroupID1)
|
||||
}
|
||||
|
||||
secret, err = client.Logical().Read("identity/group/id/" + ldapExtGroupID2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
extGroup2Entities := secret.Data["member_entity_ids"].([]interface{})
|
||||
found = false
|
||||
for _, item := range extGroup2Entities {
|
||||
if item.(string) == entityID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("missing entity ID %q first external group with ID %q", entityID, ldapExtGroupID2)
|
||||
}
|
||||
}
|
|
@ -2309,6 +2309,11 @@ func (i *IdentityStore) refreshExternalGroupMembershipsByEntityID(entityID strin
|
|||
return err
|
||||
}
|
||||
|
||||
mountAccessor := ""
|
||||
if len(groupAliases) != 0 {
|
||||
mountAccessor = groupAliases[0].MountAccessor
|
||||
}
|
||||
|
||||
var newGroups []*identity.Group
|
||||
for _, alias := range groupAliases {
|
||||
aliasByFactors, err := i.MemDBAliasByFactors(alias.MountAccessor, alias.Name, true, true)
|
||||
|
@ -2352,6 +2357,12 @@ func (i *IdentityStore) refreshExternalGroupMembershipsByEntityID(entityID strin
|
|||
continue
|
||||
}
|
||||
|
||||
// If the external group is from a different mount, don't remove the
|
||||
// entity ID from it.
|
||||
if mountAccessor != "" && group.Alias.MountAccessor != mountAccessor {
|
||||
continue
|
||||
}
|
||||
|
||||
i.logger.Debug("removing member entity ID from external group", "member_entity_id", entityID, "group_id", group.ID)
|
||||
|
||||
group.MemberEntityIDs = strutil.StrListDelete(group.MemberEntityIDs, entityID)
|
||||
|
|
|
@ -684,6 +684,9 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
|
|||
logical.UpdateOperation: b.handlePolicySet,
|
||||
logical.DeleteOperation: b.handlePolicyDelete,
|
||||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(sysHelp["policy"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysHelp["policy"][1]),
|
||||
},
|
||||
|
||||
&framework.Path{
|
||||
|
@ -1096,6 +1099,8 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
|
|||
logical.DeleteOperation: b.handleRawDelete,
|
||||
logical.ListOperation: b.handleRawList,
|
||||
},
|
||||
HelpSynopsis: strings.TrimSpace(sysHelp["raw"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysHelp["raw"][1]),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -4039,4 +4044,8 @@ This path responds to the following HTTP methods.
|
|||
"passthrough_request_headers": {
|
||||
"A list of headers to whitelist and pass from the request to the backend.",
|
||||
},
|
||||
"raw": {
|
||||
"Write, Read, and Delete data directly in the Storage backend.",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -204,7 +204,7 @@ func (c *Core) handleRequest(ctx context.Context, req *logical.Request) (retResp
|
|||
// return invalid request so that the status codes can be correct
|
||||
errType := logical.ErrInvalidRequest
|
||||
switch ctErr {
|
||||
case ErrInternalError, logical.ErrPermissionDenied:
|
||||
case ErrInternalError, logical.ErrPermissionDenied, logical.ErrEntityDisabled:
|
||||
errType = ctErr
|
||||
}
|
||||
|
||||
|
@ -519,6 +519,10 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re
|
|||
return nil, nil, fmt.Errorf("failed to create an entity for the authenticated alias")
|
||||
}
|
||||
|
||||
if entity.Disabled {
|
||||
return nil, nil, logical.ErrEntityDisabled
|
||||
}
|
||||
|
||||
auth.EntityID = entity.ID
|
||||
if auth.GroupAliases != nil {
|
||||
err = c.identityStore.refreshExternalGroupMembershipsByEntityID(auth.EntityID, auth.GroupAliases)
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/helper/consts"
|
||||
"github.com/hashicorp/vault/helper/identity"
|
||||
"github.com/hashicorp/vault/helper/jsonutil"
|
||||
"github.com/hashicorp/vault/helper/locksutil"
|
||||
"github.com/hashicorp/vault/helper/parseutil"
|
||||
|
@ -104,6 +105,8 @@ type TokenStore struct {
|
|||
salt *salt.Salt
|
||||
|
||||
tidyLock int64
|
||||
|
||||
identityPoliciesDeriverFunc func(string) (*identity.Entity, []string, error)
|
||||
}
|
||||
|
||||
// NewTokenStore is used to construct a token store that is
|
||||
|
@ -114,11 +117,12 @@ func NewTokenStore(ctx context.Context, logger log.Logger, c *Core, config *logi
|
|||
|
||||
// Initialize the store
|
||||
t := &TokenStore{
|
||||
view: view,
|
||||
cubbyholeDestroyer: destroyCubbyhole,
|
||||
logger: logger,
|
||||
tokenLocks: locksutil.CreateLocks(),
|
||||
saltLock: sync.RWMutex{},
|
||||
view: view,
|
||||
cubbyholeDestroyer: destroyCubbyhole,
|
||||
logger: logger,
|
||||
tokenLocks: locksutil.CreateLocks(),
|
||||
saltLock: sync.RWMutex{},
|
||||
identityPoliciesDeriverFunc: c.fetchEntityAndDerivedPolicies,
|
||||
}
|
||||
|
||||
if c.policyStore != nil {
|
||||
|
@ -2204,6 +2208,16 @@ func (ts *TokenStore) handleLookup(ctx context.Context, req *logical.Request, da
|
|||
resp.Data["issue_time"] = leaseTimes.IssueTime
|
||||
}
|
||||
|
||||
if out.EntityID != "" {
|
||||
_, identityPolicies, err := ts.identityPoliciesDeriverFunc(out.EntityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(identityPolicies) != 0 {
|
||||
resp.Data["identity_policies"] = identityPolicies
|
||||
}
|
||||
}
|
||||
|
||||
if urltoken {
|
||||
resp.AddWarning(`Using a token in the path is unsafe as the token can be logged in many places. Please use POST or PUT with the token passed in via the "token" parameter.`)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
package vault_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
credLdap "github.com/hashicorp/vault/builtin/credential/ldap"
|
||||
vaulthttp "github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
func TestTokenStore_IdentityPolicies(t *testing.T) {
|
||||
coreConfig := &vault.CoreConfig{
|
||||
CredentialBackends: map[string]logical.Factory{
|
||||
"ldap": credLdap.Factory,
|
||||
},
|
||||
}
|
||||
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
})
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
||||
core := cluster.Cores[0].Core
|
||||
vault.TestWaitActive(t, core)
|
||||
client := cluster.Cores[0].Client
|
||||
|
||||
// Enable LDAP auth
|
||||
err := client.Sys().EnableAuthWithOptions("ldap", &api.EnableAuthOptions{
|
||||
Type: "ldap",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Configure LDAP auth
|
||||
_, err = client.Logical().Write("auth/ldap/config", map[string]interface{}{
|
||||
"url": "ldap://ldap.forumsys.com",
|
||||
"userattr": "uid",
|
||||
"userdn": "dc=example,dc=com",
|
||||
"groupdn": "dc=example,dc=com",
|
||||
"binddn": "cn=read-only-admin,dc=example,dc=com",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create group in LDAP auth
|
||||
_, err = client.Logical().Write("auth/ldap/groups/testgroup1", map[string]interface{}{
|
||||
"policies": "testgroup1-policy",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create user in LDAP auth
|
||||
_, err = client.Logical().Write("auth/ldap/users/tesla", map[string]interface{}{
|
||||
"policies": "default",
|
||||
"groups": "testgroup1",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Login using LDAP
|
||||
secret, err := client.Logical().Write("auth/ldap/login/tesla", map[string]interface{}{
|
||||
"password": "password",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ldapClientToken := secret.Auth.ClientToken
|
||||
|
||||
// At this point there shouldn't be any identity policy on the token
|
||||
secret, err = client.Logical().Read("auth/token/lookup/" + ldapClientToken)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, ok := secret.Data["identity_policies"]
|
||||
if ok {
|
||||
t.Fatalf("identity_policies should not have been set")
|
||||
}
|
||||
|
||||
// Extract the entity ID of the token and set some policies on the entity
|
||||
entityID := secret.Data["entity_id"].(string)
|
||||
_, err = client.Logical().Write("identity/entity/id/"+entityID, map[string]interface{}{
|
||||
"policies": []string{
|
||||
"entity_policy_1",
|
||||
"entity_policy_2",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Lookup the token and expect entity policies on the token
|
||||
secret, err = client.Logical().Read("auth/token/lookup/" + ldapClientToken)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
identityPolicies := secret.Data["identity_policies"].([]interface{})
|
||||
var actualPolicies []string
|
||||
for _, item := range identityPolicies {
|
||||
actualPolicies = append(actualPolicies, item.(string))
|
||||
}
|
||||
sort.Strings(actualPolicies)
|
||||
|
||||
expectedPolicies := []string{
|
||||
"entity_policy_1",
|
||||
"entity_policy_2",
|
||||
}
|
||||
sort.Strings(expectedPolicies)
|
||||
if !reflect.DeepEqual(expectedPolicies, actualPolicies) {
|
||||
t.Fatalf("bad: identity policies; expected: %#v\nactual: %#v", expectedPolicies, actualPolicies)
|
||||
}
|
||||
|
||||
// Create identity group and add entity as its member
|
||||
secret, err = client.Logical().Write("identity/group", map[string]interface{}{
|
||||
"policies": []string{
|
||||
"group_policy_1",
|
||||
"group_policy_2",
|
||||
},
|
||||
"member_entity_ids": []string{
|
||||
entityID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Lookup token and expect both entity and group policies on the token
|
||||
secret, err = client.Logical().Read("auth/token/lookup/" + ldapClientToken)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
identityPolicies = secret.Data["identity_policies"].([]interface{})
|
||||
actualPolicies = nil
|
||||
for _, item := range identityPolicies {
|
||||
actualPolicies = append(actualPolicies, item.(string))
|
||||
}
|
||||
sort.Strings(actualPolicies)
|
||||
|
||||
expectedPolicies = []string{
|
||||
"entity_policy_1",
|
||||
"entity_policy_2",
|
||||
"group_policy_1",
|
||||
"group_policy_2",
|
||||
}
|
||||
sort.Strings(expectedPolicies)
|
||||
if !reflect.DeepEqual(expectedPolicies, actualPolicies) {
|
||||
t.Fatalf("bad: identity policies; expected: %#v\nactual: %#v", expectedPolicies, actualPolicies)
|
||||
}
|
||||
|
||||
// Create an external group and renew the token. This should add external
|
||||
// group policies to the token.
|
||||
auths, err := client.Sys().ListAuth()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ldapMountAccessor1 := auths["ldap/"].Accessor
|
||||
|
||||
// Create an external group
|
||||
secret, err = client.Logical().Write("identity/group", map[string]interface{}{
|
||||
"type": "external",
|
||||
"policies": []string{
|
||||
"external_group_policy_1",
|
||||
"external_group_policy_2",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ldapExtGroupID1 := secret.Data["id"].(string)
|
||||
|
||||
// Associate a group from LDAP auth as a group-alias in the external group
|
||||
_, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
|
||||
"name": "testgroup1",
|
||||
"mount_accessor": ldapMountAccessor1,
|
||||
"canonical_id": ldapExtGroupID1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Renew token to refresh external group memberships
|
||||
secret, err = client.Auth().Token().Renew(ldapClientToken, 10)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Lookup token and expect entity, group and external group policies on the
|
||||
// token
|
||||
secret, err = client.Logical().Read("auth/token/lookup/" + ldapClientToken)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
identityPolicies = secret.Data["identity_policies"].([]interface{})
|
||||
actualPolicies = nil
|
||||
for _, item := range identityPolicies {
|
||||
actualPolicies = append(actualPolicies, item.(string))
|
||||
}
|
||||
sort.Strings(actualPolicies)
|
||||
|
||||
expectedPolicies = []string{
|
||||
"entity_policy_1",
|
||||
"entity_policy_2",
|
||||
"group_policy_1",
|
||||
"group_policy_2",
|
||||
"external_group_policy_1",
|
||||
"external_group_policy_2",
|
||||
}
|
||||
sort.Strings(expectedPolicies)
|
||||
if !reflect.DeepEqual(expectedPolicies, actualPolicies) {
|
||||
t.Fatalf("bad: identity policies; expected: %#v\nactual: %#v", expectedPolicies, actualPolicies)
|
||||
}
|
||||
}
|
21
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil/iam_resources.go
generated
vendored
21
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil/iam_resources.go
generated
vendored
|
@ -216,10 +216,17 @@ type ServiceConfig struct {
|
|||
ServicePath string
|
||||
}
|
||||
|
||||
func (r *iamResourceImpl) SetIamPolicyRequest(p *Policy) (*http.Request, error) {
|
||||
data := struct {
|
||||
Policy *Policy `json:"policy,omitempty"`
|
||||
}{Policy: p}
|
||||
func (r *iamResourceImpl) SetIamPolicyRequest(p *Policy) (req *http.Request, err error) {
|
||||
var data interface{}
|
||||
switch strings.ToLower(r.config.Service.Name) {
|
||||
// GCS uses a different payload than every other API.
|
||||
case "storage":
|
||||
data = p
|
||||
default:
|
||||
data = struct {
|
||||
Policy *Policy `json:"policy,omitempty"`
|
||||
}{Policy: p}
|
||||
}
|
||||
|
||||
buf, err := googleapi.WithoutDataWrapper.JSONReader(data)
|
||||
if err != nil {
|
||||
|
@ -240,6 +247,12 @@ func (r *iamResourceImpl) constructRequest(httpMtd *HttpMethodCfg, data io.Reade
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if req.Header == nil {
|
||||
req.Header = make(http.Header)
|
||||
}
|
||||
if data != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
replacementMap := make(map[string]string)
|
||||
for cId, replaceK := range httpMtd.ReplacementKeys {
|
||||
rId, ok := r.relativeId.IdTuples[cId]
|
||||
|
|
|
@ -157,9 +157,9 @@ func (b *backend) serviceAccountPolicyRollback(ctx context.Context, req *logical
|
|||
return err
|
||||
}
|
||||
|
||||
// Take our any bindings still being used by this role set from roles being removed.
|
||||
// Take out any bindings still being used by this role set from roles being removed.
|
||||
rolesToRemove := util.ToSet(entry.Roles)
|
||||
if rs.AccountId.ResourceName() == entry.AccountId.ResourceName() {
|
||||
if rs != nil && rs.AccountId.ResourceName() == entry.AccountId.ResourceName() {
|
||||
currRoles, ok := rs.Bindings[entry.Resource]
|
||||
if ok {
|
||||
rolesToRemove = rolesToRemove.Sub(currRoles)
|
||||
|
|
|
@ -1315,22 +1315,22 @@
|
|||
"revisionTime": "2018-04-03T19:54:48Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "0wzWab/JiPNjFvIx3yg5ztABJ7M=",
|
||||
"checksumSHA1": "RPIFaxCjd6lchN99dcfhJ0vqqTI=",
|
||||
"path": "github.com/hashicorp/vault-plugin-secrets-gcp/plugin",
|
||||
"revision": "963249c4107fa592ba935ccdddd07155214a83bd",
|
||||
"revisionTime": "2018-04-03T22:22:59Z"
|
||||
"revision": "b5ca08100f178f8c3eb138c4f5c4a3d039ed173e",
|
||||
"revisionTime": "2018-04-17T17:32:19Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "sV9w7yCitfGr2/4RjdKkdZjYAmA=",
|
||||
"checksumSHA1": "1xWWeopXV4tbHTrVM1eFuH8TCuo=",
|
||||
"path": "github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil",
|
||||
"revision": "963249c4107fa592ba935ccdddd07155214a83bd",
|
||||
"revisionTime": "2018-04-03T22:22:59Z"
|
||||
"revision": "b5ca08100f178f8c3eb138c4f5c4a3d039ed173e",
|
||||
"revisionTime": "2018-04-17T17:32:19Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "81kYL49zTBoj1NYczxB2Xbr2d6Y=",
|
||||
"path": "github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util",
|
||||
"revision": "963249c4107fa592ba935ccdddd07155214a83bd",
|
||||
"revisionTime": "2018-04-03T22:22:59Z"
|
||||
"revision": "b5ca08100f178f8c3eb138c4f5c4a3d039ed173e",
|
||||
"revisionTime": "2018-04-17T17:32:19Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "m3cQgQrCSuWHiPA339FaZU6LuHU=",
|
||||
|
|
|
@ -662,8 +662,7 @@ list in order to satisfy that constraint.
|
|||
- `period` `(string: "")` - If set, indicates that the token generated using
|
||||
this role should never expire. The token should be renewed within the duration
|
||||
specified by this value. At each renewal, the token's TTL will be set to the
|
||||
value of this parameter. The maximum allowed lifetime of tokens issued using
|
||||
this role.
|
||||
value of this parameter.
|
||||
- `policies` `(array: [])` - Policies to be set on tokens issued using this
|
||||
role.
|
||||
- `allow_instance_migration` `(bool: false)` - If set, allows migration of the
|
||||
|
|
|
@ -180,18 +180,30 @@ $ curl \
|
|||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": "ClientToken",
|
||||
"policies": [
|
||||
"web",
|
||||
"stage"
|
||||
],
|
||||
"path": "auth/github/login",
|
||||
"accessor": "8609694a-cdbc-db9b-d345-e782dbb562ed",
|
||||
"creation_time": 1523979354,
|
||||
"creation_ttl": 2764800,
|
||||
"display_name": "ldap2-tesla",
|
||||
"entity_id": "7d2e3179-f69b-450c-7179-ac8ee8bd8ca9",
|
||||
"expire_time": "2018-05-19T11:35:54.466476215-04:00",
|
||||
"explicit_max_ttl": 0,
|
||||
"id": "cf64a70f-3a12-3f6c-791d-6cef6d390eed",
|
||||
"identity_policies": [
|
||||
"dev-group-policy"
|
||||
],
|
||||
"issue_time": "2018-04-17T11:35:54.466476078-04:00",
|
||||
"meta": {
|
||||
"user": "armon",
|
||||
"organization": "hashicorp"
|
||||
"username": "tesla"
|
||||
},
|
||||
"display_name": "github-armon",
|
||||
"num_uses": 0
|
||||
"num_uses": 0,
|
||||
"orphan": true,
|
||||
"path": "auth/ldap2/login/tesla",
|
||||
"policies": [
|
||||
"default",
|
||||
"testgroup2-policy"
|
||||
],
|
||||
"renewable": true,
|
||||
"ttl": 2764790
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -217,18 +229,30 @@ $ curl \
|
|||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": "ClientToken",
|
||||
"policies": [
|
||||
"web",
|
||||
"stage"
|
||||
],
|
||||
"path": "auth/github/login",
|
||||
"accessor": "8609694a-cdbc-db9b-d345-e782dbb562ed",
|
||||
"creation_time": 1523979354,
|
||||
"creation_ttl": 2764800,
|
||||
"display_name": "ldap2-tesla",
|
||||
"entity_id": "7d2e3179-f69b-450c-7179-ac8ee8bd8ca9",
|
||||
"expire_time": "2018-05-19T11:35:54.466476215-04:00",
|
||||
"explicit_max_ttl": 0,
|
||||
"id": "cf64a70f-3a12-3f6c-791d-6cef6d390eed",
|
||||
"identity_policies": [
|
||||
"dev-group-policy"
|
||||
],
|
||||
"issue_time": "2018-04-17T11:35:54.466476078-04:00",
|
||||
"meta": {
|
||||
"user": "armon",
|
||||
"organization": "hashicorp"
|
||||
"username": "tesla"
|
||||
},
|
||||
"display_name": "github-armon",
|
||||
"num_uses": 0
|
||||
"num_uses": 0,
|
||||
"orphan": true,
|
||||
"path": "auth/ldap2/login/tesla",
|
||||
"policies": [
|
||||
"default",
|
||||
"testgroup2-policy"
|
||||
],
|
||||
"renewable": true,
|
||||
"ttl": 2764790
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -249,7 +273,7 @@ Returns information about the client token from the accessor.
|
|||
|
||||
```json
|
||||
{
|
||||
"accessor": "2c84f488-2133-4ced-87b0-570f93a76830"
|
||||
"accessor": "8609694a-cdbc-db9b-d345-e782dbb562ed"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -267,25 +291,32 @@ $ curl \
|
|||
|
||||
```json
|
||||
{
|
||||
"lease_id": "",
|
||||
"renewable": false,
|
||||
"lease_duration": 0,
|
||||
"data": {
|
||||
"creation_time": 1457533232,
|
||||
"accessor": "8609694a-cdbc-db9b-d345-e782dbb562ed",
|
||||
"creation_time": 1523979354,
|
||||
"creation_ttl": 2764800,
|
||||
"display_name": "token",
|
||||
"meta": null,
|
||||
"display_name": "ldap2-tesla",
|
||||
"entity_id": "7d2e3179-f69b-450c-7179-ac8ee8bd8ca9",
|
||||
"expire_time": "2018-05-19T11:35:54.466476215-04:00",
|
||||
"explicit_max_ttl": 0,
|
||||
"id": "",
|
||||
"identity_policies": [
|
||||
"dev-group-policy"
|
||||
],
|
||||
"issue_time": "2018-04-17T11:35:54.466476078-04:00",
|
||||
"meta": {
|
||||
"username": "tesla"
|
||||
},
|
||||
"num_uses": 0,
|
||||
"orphan": false,
|
||||
"path": "auth/token/create",
|
||||
"orphan": true,
|
||||
"path": "auth/ldap2/login/tesla",
|
||||
"policies": [
|
||||
"default",
|
||||
"web"
|
||||
"testgroup2-policy"
|
||||
],
|
||||
"ttl": 2591976
|
||||
},
|
||||
"warnings": null,
|
||||
"auth": null
|
||||
"renewable": true,
|
||||
"ttl": 2763902
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -26,6 +26,9 @@ This endpoint creates or updates an Entity.
|
|||
|
||||
- `policies` `(list of strings: [])` – Policies to be tied to the entity.
|
||||
|
||||
- `disabled` `(bool: false)` – Whether the entity is disabled. Disabled
|
||||
entities' associated tokens cannot be used, but are not revoked.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
```json
|
||||
|
@ -86,6 +89,7 @@ $ curl \
|
|||
"data": {
|
||||
"bucket_key_hash": "177553e4c58987f4cc5d7e530136c642",
|
||||
"creation_time": "2017-07-25T20:29:22.614756844Z",
|
||||
"disabled": false,
|
||||
"id": "8d6a45e5-572f-8f13-d226-cd0d1ec57297",
|
||||
"last_update_time": "2017-07-25T20:29:22.614756844Z",
|
||||
"metadata": {
|
||||
|
@ -120,6 +124,8 @@ This endpoint is used to update an existing entity.
|
|||
|
||||
- `policies` `(list of strings: [])` – Policies to be tied to the entity.
|
||||
|
||||
- `disabled` `(bool: false)` – Whether the entity is disabled. Disabled
|
||||
entities' associated tokens cannot be used, but are not revoked.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
|
|
|
@ -222,7 +222,7 @@ can be achieved without `sudo` via `sys/mounts/auth/[auth-path]/tune`._
|
|||
object.
|
||||
|
||||
- `listing_visibility` `(string: "")` - Speficies whether to show this mount
|
||||
in the UI-specific listing endpoint.
|
||||
in the UI-specific listing endpoint. Valid values are `"unauth"` or `""`.
|
||||
|
||||
- `passthrough_request_headers` `(array: [])` - Comma-separated list of headers
|
||||
to whitelist and pass from the request to the backend.
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
layout: "api"
|
||||
page_title: "/sys/internal/ui/mounts - HTTP API"
|
||||
sidebar_current: "docs-http-system-internal-ui-mounts"
|
||||
description: |-
|
||||
The `/sys/internal/ui/mounts` endpoint is used to manage mount listing visibility.
|
||||
---
|
||||
|
||||
# `/sys/internal/ui/mounts`
|
||||
|
||||
The `/sys/internal/ui/mounts` endpoint is used to manage mount listing
|
||||
visibility. The response generated by this endpoint is based on the
|
||||
`listing_visibility` value on the mount, which can be set during mount time or
|
||||
via mount tuning. This is currently only being used internally for the UI and is
|
||||
an unauthenticated endpoint.
|
||||
|
||||
Due to the nature of its intended usage, there is no guarantee on backwards
|
||||
compatibility for this endpoint.
|
||||
|
||||
## Get Available Visible Mounts
|
||||
|
||||
This endpoint lists all enabled auth methods.
|
||||
|
||||
| Method | Path | Produces |
|
||||
| :----- | :------------------------ | :--------------------- |
|
||||
| `GET` | `/sys/internal/ui/mounts` | `200 application/json` |
|
||||
|
||||
|
||||
### Sample Request
|
||||
|
||||
```
|
||||
$ curl \
|
||||
http://127.0.0.1:8200/v1/sys/internal/ui/mounts
|
||||
```
|
||||
|
||||
### Sample Response
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"github/": {
|
||||
"description": "GitHub auth",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"secret": {
|
||||
"custom-secrets/": {
|
||||
"description": "Custom secrets",
|
||||
"type": "kv"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
|
@ -223,7 +223,7 @@ This endpoint tunes configuration parameters for a given mount point.
|
|||
object.
|
||||
|
||||
- `listing_visibility` `(string: "")` - Speficies whether to show this mount
|
||||
in the UI-specific listing endpoint.
|
||||
in the UI-specific listing endpoint. Valid values are `"unauth"` or `""`.
|
||||
|
||||
- `passthrough_request_headers` `(array: [])` - Comma-separated list of headers
|
||||
to whitelist and pass from the request to the backend.
|
||||
|
|
|
@ -92,7 +92,7 @@ A single CLI command can be used to create a new OTP and invoke SSH with the
|
|||
correct parameters to connect to the host.
|
||||
|
||||
```text
|
||||
$ vault ssh -role otp_key_role username@x.x.x.x
|
||||
$ vault ssh -role otp_key_role -mode otp username@x.x.x.x
|
||||
OTP for the session is `b4d47e1b-4879-5f4e-ce5c-7988d7986f37`
|
||||
[Note: Install `sshpass` to automate typing in OTP]
|
||||
Password: <Enter OTP>
|
||||
|
@ -101,7 +101,7 @@ Password: <Enter OTP>
|
|||
The OTP will be entered automatically using `sshpass` if it is installed.
|
||||
|
||||
```text
|
||||
$ vault ssh -role otp_key_role -strict-host-key-checking=no username@x.x.x.x
|
||||
$ vault ssh -role otp_key_role -mode otp -strict-host-key-checking=no username@x.x.x.x
|
||||
username@<IP of remote host>:~$
|
||||
```
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ $ vault kv put secret/hello foo=world excited=yes
|
|||
Success! Data written to: secret/hello
|
||||
```
|
||||
|
||||
`vault write` is a very powerful command. In addition to writing data
|
||||
`vault kv put` is a very powerful command. In addition to writing data
|
||||
directly from the command-line, it can read values and key pairs from
|
||||
`STDIN` as well as files. For more information, see the
|
||||
[command documentation](/docs/commands/index.html).
|
||||
|
|
|
@ -219,6 +219,9 @@
|
|||
<li<%= sidebar_current("docs-http-system-init") %>>
|
||||
<a href="/api/system/init.html"><tt>/sys/init</tt></a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-http-system-internal-ui-mounts") %>>
|
||||
<a href="/api/system/internal-ui-mounts.html"><tt>/sys/internal/ui/mounts</tt></a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-http-system-key-status") %>>
|
||||
<a href="/api/system/key-status.html"><tt>/sys/key-status</tt></a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue