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)
|
## 0.10.0 (April 10th, 2018)
|
||||||
|
|
||||||
SECURITY:
|
SECURITY:
|
||||||
|
|
|
@ -606,7 +606,7 @@ CLUSTER_SYNTHESIS_COMPLETE:
|
||||||
// Force https as we'll always be TLS-secured
|
// Force https as we'll always be TLS-secured
|
||||||
u, err := url.ParseRequestURI(coreConfig.ClusterAddr)
|
u, err := url.ParseRequestURI(coreConfig.ClusterAddr)
|
||||||
if err != nil {
|
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
|
return 11
|
||||||
}
|
}
|
||||||
u.Scheme = "https"
|
u.Scheme = "https"
|
||||||
|
|
|
@ -200,6 +200,9 @@ type Entity struct {
|
||||||
// the entities belonging to a particular bucket during invalidation of the
|
// the entities belonging to a particular bucket during invalidation of the
|
||||||
// storage key.
|
// storage key.
|
||||||
BucketKeyHash string `sentinel:"" protobuf:"bytes,9,opt,name=bucket_key_hash,json=bucketKeyHash" json:"bucket_key_hash,omitempty"`
|
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{} }
|
func (m *Entity) Reset() { *m = Entity{} }
|
||||||
|
@ -270,6 +273,13 @@ func (m *Entity) GetBucketKeyHash() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Entity) GetDisabled() bool {
|
||||||
|
if m != nil {
|
||||||
|
return m.Disabled
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Alias represents the alias that gets stored inside of the
|
// Alias represents the alias that gets stored inside of the
|
||||||
// entity object in storage and also represents in an in-memory index of an
|
// entity object in storage and also represents in an in-memory index of an
|
||||||
// alias object.
|
// alias object.
|
||||||
|
@ -392,43 +402,44 @@ func init() {
|
||||||
func init() { proto.RegisterFile("types.proto", fileDescriptor0) }
|
func init() { proto.RegisterFile("types.proto", fileDescriptor0) }
|
||||||
|
|
||||||
var fileDescriptor0 = []byte{
|
var fileDescriptor0 = []byte{
|
||||||
// 603 bytes of a gzipped FileDescriptorProto
|
// 617 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x93, 0xdd, 0x6e, 0xd3, 0x30,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x94, 0xdd, 0x6e, 0xd3, 0x30,
|
||||||
0x14, 0xc7, 0xd5, 0xa6, 0x9f, 0x27, 0x5d, 0x37, 0x2c, 0x84, 0x4c, 0xa5, 0x41, 0x37, 0x69, 0x28,
|
0x14, 0xc7, 0xd5, 0xa6, 0x1f, 0xe9, 0x69, 0xd7, 0x0d, 0x0b, 0x21, 0x53, 0x69, 0xd0, 0x4d, 0x1a,
|
||||||
0x70, 0x91, 0x49, 0xe3, 0x86, 0x8d, 0x0b, 0x34, 0xc1, 0x80, 0x09, 0x21, 0xa1, 0x68, 0x5c, 0x47,
|
0x2a, 0x5c, 0x64, 0xd2, 0xb8, 0x61, 0xe3, 0x02, 0x4d, 0x30, 0x60, 0x42, 0x48, 0x28, 0x1a, 0xd7,
|
||||||
0x6e, 0xe2, 0xb5, 0xd6, 0x92, 0x38, 0x8a, 0x1d, 0x44, 0x5e, 0x87, 0x97, 0xe1, 0x69, 0x78, 0x07,
|
0x91, 0x1b, 0x7b, 0xad, 0xb5, 0x24, 0x8e, 0x62, 0x17, 0x91, 0xd7, 0xe1, 0xd5, 0xb8, 0xe6, 0x1d,
|
||||||
0xe4, 0xe3, 0xa6, 0x0d, 0x74, 0x7c, 0x4c, 0xdb, 0x9d, 0xf3, 0x3f, 0xc7, 0xc7, 0x27, 0xe7, 0xff,
|
0x90, 0x8f, 0x9b, 0x36, 0xd0, 0xf1, 0x31, 0x6d, 0x77, 0xf6, 0xff, 0x1c, 0x1f, 0x1f, 0x9f, 0xff,
|
||||||
0x3b, 0xe0, 0xea, 0x2a, 0xe7, 0xca, 0xcf, 0x0b, 0xa9, 0x25, 0x19, 0x88, 0x98, 0x67, 0x5a, 0xe8,
|
0x2f, 0x81, 0xbe, 0x29, 0x73, 0xa1, 0x83, 0xbc, 0x50, 0x46, 0x11, 0x5f, 0x72, 0x91, 0x19, 0x69,
|
||||||
0x6a, 0xf2, 0x78, 0x2e, 0xe5, 0x3c, 0xe1, 0x87, 0xa8, 0xcf, 0xca, 0xcb, 0x43, 0x2d, 0x52, 0xae,
|
0xca, 0xd1, 0xe3, 0x99, 0x52, 0xb3, 0x44, 0x1c, 0xa2, 0x3e, 0x5d, 0x5c, 0x1e, 0x1a, 0x99, 0x0a,
|
||||||
0x34, 0x4b, 0x73, 0x9b, 0xba, 0xff, 0xad, 0x03, 0xdd, 0x77, 0x85, 0x2c, 0x73, 0x32, 0x86, 0xb6,
|
0x6d, 0x58, 0x9a, 0xbb, 0xd4, 0xfd, 0x6f, 0x2d, 0x68, 0xbf, 0x2b, 0xd4, 0x22, 0x27, 0x43, 0x68,
|
||||||
0x88, 0x69, 0x6b, 0xda, 0xf2, 0x86, 0x41, 0x5b, 0xc4, 0x84, 0x40, 0x27, 0x63, 0x29, 0xa7, 0x6d,
|
0x4a, 0x4e, 0x1b, 0xe3, 0xc6, 0xa4, 0x17, 0x36, 0x25, 0x27, 0x04, 0x5a, 0x19, 0x4b, 0x05, 0x6d,
|
||||||
0x54, 0xf0, 0x4c, 0x26, 0x30, 0xc8, 0x65, 0x22, 0x22, 0xc1, 0x15, 0x75, 0xa6, 0x8e, 0x37, 0x0c,
|
0xa2, 0x82, 0x6b, 0x32, 0x02, 0x3f, 0x57, 0x89, 0x8c, 0xa5, 0xd0, 0xd4, 0x1b, 0x7b, 0x93, 0x5e,
|
||||||
0x56, 0xdf, 0xc4, 0x83, 0x9d, 0x9c, 0x15, 0x3c, 0xd3, 0xe1, 0xdc, 0xd4, 0x0b, 0x45, 0xac, 0x68,
|
0xb8, 0xda, 0x93, 0x09, 0xec, 0xe4, 0xac, 0x10, 0x99, 0x89, 0x66, 0xb6, 0x5e, 0x24, 0xb9, 0xa6,
|
||||||
0x07, 0x73, 0xc6, 0x56, 0xc7, 0x67, 0xce, 0x63, 0x45, 0x9e, 0xc1, 0xbd, 0x94, 0xa7, 0x33, 0x5e,
|
0x2d, 0xcc, 0x19, 0x3a, 0x1d, 0xaf, 0x39, 0xe7, 0x9a, 0x3c, 0x83, 0x7b, 0xa9, 0x48, 0xa7, 0xa2,
|
||||||
0x84, 0xb6, 0x4b, 0x4c, 0xed, 0x62, 0xea, 0xb6, 0x0d, 0x9c, 0xa1, 0x6e, 0x72, 0x8f, 0x61, 0x90,
|
0x88, 0x5c, 0x97, 0x98, 0xda, 0xc6, 0xd4, 0x6d, 0x17, 0x38, 0x43, 0xdd, 0xe6, 0x1e, 0x83, 0x9f,
|
||||||
0x72, 0xcd, 0x62, 0xa6, 0x19, 0xed, 0x4d, 0x1d, 0xcf, 0x3d, 0xda, 0xf5, 0xeb, 0xbf, 0xf3, 0xb1,
|
0x0a, 0xc3, 0x38, 0x33, 0x8c, 0x76, 0xc6, 0xde, 0xa4, 0x7f, 0xb4, 0x1b, 0x54, 0xaf, 0x0b, 0xb0,
|
||||||
0xa2, 0xff, 0x71, 0x19, 0x3f, 0xcb, 0x74, 0x51, 0x05, 0xab, 0x74, 0xf2, 0x0a, 0xb6, 0xa2, 0x82,
|
0x62, 0xf0, 0x71, 0x19, 0x3f, 0xcb, 0x4c, 0x51, 0x86, 0xab, 0x74, 0xf2, 0x0a, 0xb6, 0xe2, 0x42,
|
||||||
0x33, 0x2d, 0x64, 0x16, 0x9a, 0xdf, 0xa6, 0xfd, 0x69, 0xcb, 0x73, 0x8f, 0x26, 0xbe, 0x9d, 0x89,
|
0x30, 0x23, 0x55, 0x16, 0xd9, 0x67, 0xd3, 0xee, 0xb8, 0x31, 0xe9, 0x1f, 0x8d, 0x02, 0x37, 0x93,
|
||||||
0x5f, 0xcf, 0xc4, 0xbf, 0xa8, 0x67, 0x12, 0x8c, 0xea, 0x0b, 0x46, 0x22, 0x6f, 0x60, 0x27, 0x61,
|
0xa0, 0x9a, 0x49, 0x70, 0x51, 0xcd, 0x24, 0x1c, 0x54, 0x07, 0xac, 0x44, 0xde, 0xc0, 0x4e, 0xc2,
|
||||||
0x4a, 0x87, 0x65, 0x1e, 0x33, 0xcd, 0x6d, 0x8d, 0xc1, 0x3f, 0x6b, 0x8c, 0xcd, 0x9d, 0xcf, 0x78,
|
0xb4, 0x89, 0x16, 0x39, 0x67, 0x46, 0xb8, 0x1a, 0xfe, 0x3f, 0x6b, 0x0c, 0xed, 0x99, 0xcf, 0x78,
|
||||||
0x05, 0xab, 0xec, 0xc1, 0x28, 0x95, 0xb1, 0xb8, 0xac, 0x42, 0x91, 0xc5, 0xfc, 0x2b, 0x1d, 0x4e,
|
0x04, 0xab, 0xec, 0xc1, 0x20, 0x55, 0x5c, 0x5e, 0x96, 0x91, 0xcc, 0xb8, 0xf8, 0x4a, 0x7b, 0xe3,
|
||||||
0x5b, 0x5e, 0x27, 0x70, 0xad, 0x76, 0x6e, 0x24, 0xf2, 0x04, 0xb6, 0x67, 0x65, 0x74, 0xc5, 0x75,
|
0xc6, 0xa4, 0x15, 0xf6, 0x9d, 0x76, 0x6e, 0x25, 0xf2, 0x04, 0xb6, 0xa7, 0x8b, 0xf8, 0x4a, 0x98,
|
||||||
0x78, 0xc5, 0xab, 0x70, 0xc1, 0xd4, 0x82, 0x02, 0x4e, 0x7d, 0xcb, 0xca, 0x1f, 0x78, 0xf5, 0x9e,
|
0xe8, 0x4a, 0x94, 0xd1, 0x9c, 0xe9, 0x39, 0x05, 0x9c, 0xfa, 0x96, 0x93, 0x3f, 0x88, 0xf2, 0x3d,
|
||||||
0xa9, 0x05, 0x39, 0x80, 0x2e, 0x4b, 0x04, 0x53, 0xd4, 0xc5, 0x2e, 0xb6, 0xd7, 0x93, 0x38, 0x35,
|
0xd3, 0x73, 0x72, 0x00, 0x6d, 0x96, 0x48, 0xa6, 0x69, 0x1f, 0xbb, 0xd8, 0x5e, 0x4f, 0xe2, 0xd4,
|
||||||
0x72, 0x60, 0xa3, 0xc6, 0x39, 0x43, 0x03, 0x1d, 0x59, 0xe7, 0xcc, 0x79, 0xf2, 0x12, 0xb6, 0x7e,
|
0xca, 0xa1, 0x8b, 0x5a, 0xe7, 0x2c, 0x0d, 0x74, 0xe0, 0x9c, 0xb3, 0xeb, 0xd1, 0x4b, 0xd8, 0xfa,
|
||||||
0x99, 0x13, 0xd9, 0x01, 0xe7, 0x8a, 0x57, 0x4b, 0xbf, 0xcd, 0x91, 0xdc, 0x87, 0xee, 0x17, 0x96,
|
0x65, 0x4e, 0x64, 0x07, 0xbc, 0x2b, 0x51, 0x2e, 0xfd, 0xb6, 0x4b, 0x72, 0x1f, 0xda, 0x5f, 0x58,
|
||||||
0x94, 0xb5, 0xe3, 0xf6, 0xe3, 0xa4, 0xfd, 0xa2, 0xb5, 0xff, 0xdd, 0x81, 0x9e, 0xb5, 0x84, 0x3c,
|
0xb2, 0xa8, 0x1c, 0x77, 0x9b, 0x93, 0xe6, 0x8b, 0xc6, 0xfe, 0x77, 0x0f, 0x3a, 0xce, 0x12, 0xf2,
|
||||||
0x85, 0x3e, 0x3e, 0xc2, 0x15, 0x6d, 0xa1, 0x1d, 0x1b, 0x4d, 0xd4, 0xf1, 0x25, 0x50, 0xed, 0x0d,
|
0x14, 0xba, 0x78, 0x89, 0xd0, 0xb4, 0x81, 0x76, 0x6c, 0x34, 0x51, 0xc5, 0x97, 0x40, 0x35, 0x37,
|
||||||
0xa0, 0x9c, 0x06, 0x50, 0x27, 0x0d, 0x7b, 0x3b, 0x58, 0xef, 0xd1, 0xba, 0x9e, 0x7d, 0xf2, 0xff,
|
0x80, 0xf2, 0x6a, 0x40, 0x9d, 0xd4, 0xec, 0x6d, 0x61, 0xbd, 0x47, 0xeb, 0x7a, 0xee, 0xca, 0xff,
|
||||||
0xfd, 0xed, 0xde, 0x81, 0xbf, 0xbd, 0x1b, 0xfb, 0x8b, 0x34, 0x17, 0x73, 0x1e, 0x37, 0x69, 0xee,
|
0xf7, 0xb7, 0x7d, 0x07, 0xfe, 0x76, 0x6e, 0xec, 0x2f, 0xd2, 0x5c, 0xcc, 0x04, 0xaf, 0xd3, 0xdc,
|
||||||
0xd7, 0x34, 0x9b, 0xc0, 0x9a, 0xe6, 0xe6, 0xfe, 0x0c, 0x7e, 0xdb, 0x9f, 0x6b, 0x20, 0x18, 0x5e,
|
0xad, 0x68, 0xb6, 0x81, 0x35, 0xcd, 0xf5, 0xef, 0xc7, 0xff, 0xed, 0xfb, 0xb9, 0x06, 0x82, 0xde,
|
||||||
0x03, 0xc1, 0xed, 0x9c, 0xfc, 0xe1, 0x40, 0x17, 0x6d, 0xda, 0x58, 0xf7, 0x3d, 0x18, 0x45, 0x2c,
|
0x75, 0x10, 0x8c, 0xc0, 0xe7, 0x52, 0xb3, 0x69, 0x22, 0x38, 0x72, 0xe0, 0x87, 0xab, 0xfd, 0xed,
|
||||||
0x93, 0x99, 0x88, 0x58, 0x12, 0xae, 0x7c, 0x73, 0x57, 0xda, 0x79, 0x4c, 0x76, 0x01, 0x52, 0x59,
|
0x5c, 0xfe, 0xe1, 0x41, 0x1b, 0x2d, 0xdc, 0xf8, 0x15, 0xec, 0xc1, 0x20, 0x66, 0x99, 0xca, 0x64,
|
||||||
0x66, 0x3a, 0x44, 0xba, 0xac, 0x8d, 0x43, 0x54, 0x2e, 0xaa, 0x9c, 0x93, 0x03, 0x18, 0xdb, 0x30,
|
0xcc, 0x92, 0x68, 0xe5, 0x69, 0x7f, 0xa5, 0x9d, 0x73, 0xb2, 0x0b, 0x90, 0xaa, 0x45, 0x66, 0x22,
|
||||||
0x8b, 0x22, 0xae, 0x94, 0x2c, 0x68, 0xc7, 0xf6, 0x8f, 0xea, 0xe9, 0x52, 0x5c, 0x57, 0xc9, 0x99,
|
0x24, 0xcf, 0x59, 0xdc, 0x43, 0xe5, 0xa2, 0xcc, 0x05, 0x39, 0x80, 0xa1, 0x0b, 0xb3, 0x38, 0x16,
|
||||||
0x5e, 0xa0, 0x67, 0x75, 0x95, 0x4f, 0x4c, 0x2f, 0xfe, 0xbe, 0xf0, 0xd8, 0xfa, 0x1f, 0x81, 0xa8,
|
0x5a, 0xab, 0x82, 0xb6, 0xdc, 0xdb, 0x50, 0x3d, 0x5d, 0x8a, 0xeb, 0x2a, 0x39, 0x33, 0x73, 0xf4,
|
||||||
0x01, 0xeb, 0x37, 0x00, 0xdb, 0x80, 0x64, 0x70, 0x07, 0x90, 0x0c, 0x6f, 0x0c, 0xc9, 0x31, 0x3c,
|
0xb3, 0xaa, 0xf2, 0x89, 0x99, 0xf9, 0xdf, 0x7f, 0x06, 0xd8, 0xfa, 0x1f, 0x61, 0xa9, 0xe0, 0xeb,
|
||||||
0x5c, 0x42, 0x72, 0x59, 0xc8, 0x34, 0x6c, 0x4e, 0x5a, 0x51, 0x40, 0x12, 0x1e, 0xd8, 0x84, 0xb7,
|
0xd6, 0xe0, 0xdb, 0x00, 0xc8, 0xbf, 0x03, 0x80, 0x7a, 0x37, 0x06, 0xe8, 0x18, 0x1e, 0x2e, 0x01,
|
||||||
0x85, 0x4c, 0x5f, 0xaf, 0x87, 0xae, 0x6e, 0xe5, 0xf7, 0xac, 0x87, 0xbd, 0x3d, 0xff, 0x19, 0x00,
|
0xba, 0x2c, 0x54, 0x1a, 0xd5, 0x27, 0xad, 0x29, 0x20, 0x25, 0x0f, 0x5c, 0xc2, 0xdb, 0x42, 0xa5,
|
||||||
0x00, 0xff, 0xff, 0x8e, 0x4a, 0xc5, 0xdb, 0x1f, 0x06, 0x00, 0x00,
|
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
|
// MFASecrets holds the MFA secrets indexed by the identifier of the MFA
|
||||||
// method configuration.
|
// method configuration.
|
||||||
//map<string, mfa.Secret> mfa_secrets = 10;
|
//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
|
// 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 is returned if the client is not authorized
|
||||||
ErrPermissionDenied = errors.New("permission denied")
|
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
|
// ErrMultiAuthzPending is returned if the the request needs more
|
||||||
// authorizations
|
// authorizations
|
||||||
ErrMultiAuthzPending = errors.New("request needs further approval")
|
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')}`;
|
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');
|
let data = this.get('data');
|
||||||
const mime = this.get('mime');
|
let filename = this.get('download');
|
||||||
|
let mime = this.get('mime');
|
||||||
if (this.get('stringify')) {
|
if (this.get('stringify')) {
|
||||||
data = JSON.stringify(data, null, 2);
|
data = JSON.stringify(data, null, 2);
|
||||||
}
|
}
|
||||||
|
if (window.navigator.msSaveOrOpenBlob) {
|
||||||
const file = new File([data], { type: mime });
|
file = new Blob([data], { type: mime });
|
||||||
return window.URL.createObjectURL(file);
|
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',
|
actionText: 'Download',
|
||||||
data: null,
|
data: null,
|
||||||
filename: null,
|
filename: null,
|
||||||
|
|
|
@ -8,13 +8,13 @@ const { computed } = Ember;
|
||||||
export default Ember.Controller.extend({
|
export default Ember.Controller.extend({
|
||||||
mountTypes: [
|
mountTypes: [
|
||||||
{ label: 'AWS', value: 'aws' },
|
{ label: 'AWS', value: 'aws' },
|
||||||
{ label: 'Cassandra', value: 'cassandra' },
|
{ label: 'Cassandra', value: 'cassandra', deprecated: true },
|
||||||
{ label: 'Consul', value: 'consul' },
|
{ label: 'Consul', value: 'consul' },
|
||||||
{ label: 'Databases', value: 'database' },
|
{ label: 'Databases', value: 'database' },
|
||||||
{ label: 'Google Cloud', value: 'gcp' },
|
{ label: 'Google Cloud', value: 'gcp' },
|
||||||
{ label: 'KV', value: 'kv' },
|
{ label: 'KV', value: 'kv' },
|
||||||
{ label: 'MongoDB', value: 'mongodb' },
|
{ label: 'MongoDB', value: 'mongodb', deprecated: true },
|
||||||
{ label: 'MS SQL', value: 'mssql', deprecated: true },
|
{ label: 'MSSQL', value: 'mssql', deprecated: true },
|
||||||
{ label: 'MySQL', value: 'mysql', deprecated: true },
|
{ label: 'MySQL', value: 'mysql', deprecated: true },
|
||||||
{ label: 'Nomad', value: 'nomad' },
|
{ label: 'Nomad', value: 'nomad' },
|
||||||
{ label: 'PKI', value: 'pki' },
|
{ label: 'PKI', value: 'pki' },
|
||||||
|
|
|
@ -46,7 +46,7 @@ export default EditBase.extend({
|
||||||
const parentKey = params.secret ? params.secret : '';
|
const parentKey = params.secret ? params.secret : '';
|
||||||
return Ember.RSVP.hash({
|
return Ember.RSVP.hash({
|
||||||
secret: this.createModel(transition, parentKey),
|
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) {
|
getModelType(backend, tab) {
|
||||||
const types = {
|
const types = {
|
||||||
transit: 'transit-key',
|
transit: 'transit-key',
|
||||||
|
@ -49,29 +43,26 @@ export default Ember.Route.extend({
|
||||||
const secret = params.secret ? params.secret : '';
|
const secret = params.secret ? params.secret : '';
|
||||||
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
||||||
const backends = this.modelFor('vault.cluster.secrets').mapBy('id');
|
const backends = this.modelFor('vault.cluster.secrets').mapBy('id');
|
||||||
return Ember.RSVP.hash({
|
return this.store
|
||||||
secrets: this.store
|
.lazyPaginatedQuery(this.getModelType(backend, params.tab), {
|
||||||
.lazyPaginatedQuery(this.getModelType(backend, params.tab), {
|
id: secret,
|
||||||
id: secret,
|
backend,
|
||||||
backend,
|
responsePath: 'data.keys',
|
||||||
responsePath: 'data.keys',
|
page: params.page,
|
||||||
page: params.page,
|
pageFilter: params.pageFilter,
|
||||||
pageFilter: params.pageFilter,
|
size: 100,
|
||||||
size: 100,
|
})
|
||||||
})
|
.then(model => {
|
||||||
.then(model => {
|
this.set('has404', false);
|
||||||
this.set('has404', false);
|
return model;
|
||||||
return model;
|
})
|
||||||
})
|
.catch(err => {
|
||||||
.catch(err => {
|
if (backends.includes(backend) && err.httpStatus === 404 && secret === '') {
|
||||||
if (backends.includes(backend) && err.httpStatus === 404 && secret === '') {
|
return [];
|
||||||
return [];
|
} else {
|
||||||
} else {
|
throw err;
|
||||||
throw err;
|
}
|
||||||
}
|
});
|
||||||
}),
|
|
||||||
capabilities: this.capabilities(secret),
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
afterModel(model) {
|
afterModel(model) {
|
||||||
|
@ -107,8 +98,7 @@ export default Ember.Route.extend({
|
||||||
const has404 = this.get('has404');
|
const has404 = this.get('has404');
|
||||||
controller.set('hasModel', true);
|
controller.set('hasModel', true);
|
||||||
controller.setProperties({
|
controller.setProperties({
|
||||||
model: model.secrets,
|
model,
|
||||||
capabilities: model.capabilities,
|
|
||||||
baseKey: { id: secret },
|
baseKey: { id: secret },
|
||||||
has404,
|
has404,
|
||||||
backend,
|
backend,
|
||||||
|
@ -125,7 +115,7 @@ export default Ember.Route.extend({
|
||||||
}
|
}
|
||||||
controller.setProperties({
|
controller.setProperties({
|
||||||
filter: filter || '',
|
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, {
|
export default Ember.Route.extend(UnloadModelRoute, {
|
||||||
capabilities(secret) {
|
capabilities(secret) {
|
||||||
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
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;
|
let path;
|
||||||
if (backend === 'transit') {
|
if (backendType === 'transit') {
|
||||||
path = backend + '/keys/' + secret;
|
path = backend + '/keys/' + secret;
|
||||||
} else if (backend === 'ssh' || backend === 'aws') {
|
} else if (backendType === 'ssh' || backendType === 'aws') {
|
||||||
path = backend + '/roles/' + secret;
|
path = backend + '/roles/' + secret;
|
||||||
|
} else if (version && version === 2) {
|
||||||
|
path = backend + '/data/' + secret;
|
||||||
} else {
|
} else {
|
||||||
path = backend + '/' + secret;
|
path = backend + '/' + secret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,20 +52,18 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="field is-grouped-split box is-fullwidth is-bottomless">
|
<div class="field is-grouped-split box is-fullwidth is-bottomless">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
{{#if capabilities.canCreate}}
|
<button
|
||||||
<button
|
type="submit"
|
||||||
type="submit"
|
disabled={{buttonDisabled}}
|
||||||
disabled={{buttonDisabled}}
|
class="button is-primary"
|
||||||
class="button is-primary"
|
data-test-role-aws-create=true
|
||||||
data-test-role-aws-create=true
|
>
|
||||||
>
|
{{#if (eq mode 'create')}}
|
||||||
{{#if (eq mode 'create')}}
|
Create role
|
||||||
Create role
|
{{else if (eq mode 'edit')}}
|
||||||
{{else if (eq mode 'edit')}}
|
Save
|
||||||
Save
|
{{/if}}
|
||||||
{{/if}}
|
</button>
|
||||||
</button>
|
|
||||||
{{/if}}
|
|
||||||
{{#secret-link
|
{{#secret-link
|
||||||
mode=(if (eq mode "create") "list" "show")
|
mode=(if (eq mode "create") "list" "show")
|
||||||
class="button"
|
class="button"
|
||||||
|
|
|
@ -5,20 +5,18 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="field is-grouped-split box is-fullwidth is-bottomless">
|
<div class="field is-grouped-split box is-fullwidth is-bottomless">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
{{#if capabilities.canCreate}}
|
<button
|
||||||
<button
|
type="submit"
|
||||||
type="submit"
|
disabled={{buttonDisabled}}
|
||||||
disabled={{buttonDisabled}}
|
class="button is-primary"
|
||||||
class="button is-primary"
|
data-test-role-create=true
|
||||||
data-test-role-create=true
|
>
|
||||||
>
|
{{#if (eq mode 'create')}}
|
||||||
{{#if (eq mode 'create')}}
|
Create role
|
||||||
Create role
|
{{else if (eq mode 'edit')}}
|
||||||
{{else if (eq mode 'edit')}}
|
Save
|
||||||
Save
|
{{/if}}
|
||||||
{{/if}}
|
</button>
|
||||||
</button>
|
|
||||||
{{/if}}
|
|
||||||
{{#secret-link
|
{{#secret-link
|
||||||
mode=(if (eq mode "create") "list" "show")
|
mode=(if (eq mode "create") "list" "show")
|
||||||
class="button"
|
class="button"
|
||||||
|
|
|
@ -25,20 +25,18 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="field is-grouped-split box is-fullwidth is-bottomless">
|
<div class="field is-grouped-split box is-fullwidth is-bottomless">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
{{#if capabilities.canCreate}}
|
<button
|
||||||
<button
|
type="submit"
|
||||||
type="submit"
|
disabled={{buttonDisabled}}
|
||||||
disabled={{buttonDisabled}}
|
class="button is-primary"
|
||||||
class="button is-primary"
|
data-test-role-ssh-create=true
|
||||||
data-test-role-ssh-create=true
|
>
|
||||||
>
|
{{#if (eq mode 'create')}}
|
||||||
{{#if (eq mode 'create')}}
|
Create role
|
||||||
Create role
|
{{else if (eq mode 'edit')}}
|
||||||
{{else if (eq mode 'edit')}}
|
Save
|
||||||
Save
|
{{/if}}
|
||||||
{{/if}}
|
</button>
|
||||||
</button>
|
|
||||||
{{/if}}
|
|
||||||
{{#secret-link
|
{{#secret-link
|
||||||
mode=(if (eq mode "create") "list" "show")
|
mode=(if (eq mode "create") "list" "show")
|
||||||
class="button"
|
class="button"
|
||||||
|
|
|
@ -34,16 +34,14 @@
|
||||||
|
|
||||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
{{#if capabilities.canCreate}}
|
<button
|
||||||
<button
|
type="submit"
|
||||||
type="submit"
|
disabled={{buttonDisabled}}
|
||||||
disabled={{buttonDisabled}}
|
class="button is-primary"
|
||||||
class="button is-primary"
|
data-test-secret-save=true
|
||||||
data-test-secret-save=true
|
>
|
||||||
>
|
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
{{#secret-link
|
{{#secret-link
|
||||||
|
|
|
@ -80,11 +80,11 @@
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="b-checkbox">
|
<div class="b-checkbox">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
|
data-test-transit-key-convergent-encryption
|
||||||
id="key-convergent"
|
id="key-convergent"
|
||||||
class="styled"
|
class="styled"
|
||||||
checked={{key.convergentEncryption}}
|
checked={{key.convergentEncryption}}
|
||||||
onchange={{action "convergentEncryptionChange" value="target.checked"}}
|
onchange={{action "convergentEncryptionChange" value="target.checked"}}
|
||||||
data-test-transit-key-convergent-encryption=true
|
|
||||||
/>
|
/>
|
||||||
<label for="key-convergent" class="is-label">
|
<label for="key-convergent" class="is-label">
|
||||||
Enable convergent encryption
|
Enable convergent encryption
|
||||||
|
@ -94,18 +94,16 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||||
{{#if capabilities.canCreate}}
|
<div class="control">
|
||||||
<div class="control">
|
<button
|
||||||
<button
|
data-test-transit-key-create
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={{requestInFlight}}
|
disabled={{requestInFlight}}
|
||||||
class="button is-primary {{if requestInFlight 'is-loading'}}"
|
class="button is-primary {{if requestInFlight 'is-loading'}}"
|
||||||
data-test-transit-key-create=true
|
>
|
||||||
>
|
Create encryption key
|
||||||
Create encryption key
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
<div class="control">
|
<div class="control">
|
||||||
{{#secret-link
|
{{#secret-link
|
||||||
mode="list"
|
mode="list"
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="level-right field is-grouped">
|
<div class="level-right field is-grouped">
|
||||||
{{#if (and (not-eq tab "certs") capabilities.canCreate)}}
|
{{#if (not-eq tab "certs")}}
|
||||||
<div class="control">
|
<div class="control">
|
||||||
{{#secret-link
|
{{#secret-link
|
||||||
mode="create"
|
mode="create"
|
||||||
|
@ -114,8 +114,7 @@
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="box is-sideless">
|
<div class="box is-sideless">
|
||||||
{{#if filterFocused}}
|
{{#if filterFocused}}
|
||||||
There are no {{pluralize options.item}} matching <code>{{filter}}</code>
|
There are no {{pluralize options.item}} matching <code>{{filter}}</code>, press <kbd>ENTER</kbd> to add one.
|
||||||
{{#if capabilities.canCreate}}, press <kbd>ENTER</kbd> to add one{{/if}}.
|
|
||||||
{{else}}
|
{{else}}
|
||||||
There are no {{pluralize options.item}} matching <code>{{filter}}</code>.
|
There are no {{pluralize options.item}} matching <code>{{filter}}</code>.
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<div class="level-left">
|
<div class="level-left">
|
||||||
<div>
|
<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">
|
<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>
|
</a>
|
||||||
<br />
|
<br />
|
||||||
<span class="tag">
|
<span class="tag">
|
||||||
|
|
|
@ -56,10 +56,10 @@
|
||||||
<div class="control is-expanded">
|
<div class="control is-expanded">
|
||||||
<div class="select is-fullwidth">
|
<div class="select is-fullwidth">
|
||||||
<select
|
<select
|
||||||
|
data-test-secret-backend-version
|
||||||
id="backend-type"
|
id="backend-type"
|
||||||
value={{selectedType}}
|
value={{selectedType}}
|
||||||
onchange={{action (mut version) value="target.value"}}
|
onchange={{action (mut version) value="target.value"}}
|
||||||
data-test-secret-backend-type=true
|
|
||||||
>
|
>
|
||||||
{{#each (array 1 2) as |versionOption|}}
|
{{#each (array 1 2) as |versionOption|}}
|
||||||
<option selected={{eq version versionOption}} value={{versionOption}}>
|
<option selected={{eq version versionOption}} value={{versionOption}}>
|
||||||
|
|
|
@ -5,8 +5,10 @@ if(process.argv[2]){
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
process.env.TERM = 'dumb';
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
var readline = require('readline')
|
||||||
var spawn = require('child_process').spawn;
|
var spawn = require('child_process').spawn;
|
||||||
var vault = spawn(
|
var vault = spawn(
|
||||||
'vault',
|
'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 output = '';
|
||||||
var unseal, root;
|
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);
|
var unsealMatch = output.match(/Unseal Key\: (.+)$/m);
|
||||||
if (unsealMatch && !unseal) { unseal = unsealMatch[1] };
|
if (unsealMatch && !unseal) { unseal = unsealMatch[1] };
|
||||||
var rootMatch = output.match(/Root Token\: (.+)$/m);
|
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 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';
|
import Ember from 'ember';
|
||||||
|
|
||||||
let adapterException;
|
let adapterException;
|
||||||
|
@ -9,7 +12,11 @@ moduleForAcceptance('Acceptance | leases', {
|
||||||
beforeEach() {
|
beforeEach() {
|
||||||
adapterException = Ember.Test.adapter.exception;
|
adapterException = Ember.Test.adapter.exception;
|
||||||
Ember.Test.adapter.exception = () => null;
|
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() {
|
afterEach() {
|
||||||
Ember.Test.adapter.exception = adapterException;
|
Ember.Test.adapter.exception = adapterException;
|
||||||
|
@ -18,43 +25,31 @@ moduleForAcceptance('Acceptance | leases', {
|
||||||
});
|
});
|
||||||
|
|
||||||
const createSecret = (context, isRenewable) => {
|
const createSecret = (context, isRenewable) => {
|
||||||
const now = new Date().getTime();
|
context.name = `secret-${new Date().getTime()}`;
|
||||||
const secretContents = { secret: 'foo' };
|
secretList.visitRoot({ backend: context.enginePath });
|
||||||
|
secretList.create();
|
||||||
if (isRenewable) {
|
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/');
|
visit('/vault/access/leases/');
|
||||||
click('[data-test-lease-link="secret/"]');
|
// all the
|
||||||
click(`[data-test-lease-link="secret/${secret.name}/"]`);
|
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)`);
|
click(`[data-test-lease-link]:eq(0)`);
|
||||||
};
|
};
|
||||||
|
|
||||||
test('it renders the show page', function(assert) {
|
test('it renders the show page', function(assert) {
|
||||||
createSecret(this);
|
createSecret(this);
|
||||||
navToDetail(this.secret);
|
navToDetail(this);
|
||||||
andThen(() => {
|
return andThen(() => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
currentRouteName(),
|
currentRouteName(),
|
||||||
'vault.cluster.access.leases.show',
|
'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);
|
createSecret(this, true);
|
||||||
navToDetail(this.secret);
|
navToDetail(this);
|
||||||
andThen(() => {
|
andThen(() => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
currentRouteName(),
|
currentRouteName(),
|
||||||
|
@ -83,7 +79,7 @@ test('it renders the show page with a picker', function(assert) {
|
||||||
|
|
||||||
test('it removes leases upon revocation', function(assert) {
|
test('it removes leases upon revocation', function(assert) {
|
||||||
createSecret(this);
|
createSecret(this);
|
||||||
navToDetail(this.secret);
|
navToDetail(this);
|
||||||
click('[data-test-lease-revoke] button');
|
click('[data-test-lease-revoke] button');
|
||||||
click('[data-test-confirm-button]');
|
click('[data-test-confirm-button]');
|
||||||
andThen(() => {
|
andThen(() => {
|
||||||
|
@ -93,10 +89,11 @@ test('it removes leases upon revocation', function(assert) {
|
||||||
'it navigates back to the leases root on revocation'
|
'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(() => {
|
andThen(() => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
find(`[data-test-lease-link="secret/${this.secret.name}/"]`).length,
|
find(`[data-test-lease-link="${this.enginePath}/data/${this.name}/"]`).length,
|
||||||
0,
|
0,
|
||||||
'link to the lease was removed with revocation'
|
'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) {
|
test('it removes branches when a prefix is revoked', function(assert) {
|
||||||
createSecret(this);
|
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-lease-revoke-prefix] button');
|
||||||
click('[data-test-confirm-button]');
|
click('[data-test-confirm-button]');
|
||||||
andThen(() => {
|
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'
|
'it navigates back to the leases root on revocation'
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
find('[data-test-lease-link="secret/"]').length,
|
find(`[data-test-lease-link="${this.enginePath}/"]`).length,
|
||||||
0,
|
0,
|
||||||
'link to the prefix was removed with revocation'
|
'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 showPage from 'vault/tests/pages/secrets/backend/kv/show';
|
||||||
import listPage from 'vault/tests/pages/secrets/backend/list';
|
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', {
|
moduleForAcceptance('Acceptance | secrets/secret/create', {
|
||||||
beforeEach() {
|
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();
|
return authLogin();
|
||||||
},
|
},
|
||||||
|
afterEach() {
|
||||||
|
this.server.shutdown();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it creates a secret and redirects', function(assert) {
|
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' });
|
listPage.visitRoot({ backend: 'secret' });
|
||||||
andThen(() => {
|
andThen(() => {
|
||||||
assert.equal(currentRouteName(), 'vault.cluster.secrets.backend.list-root', 'navigates to the list page');
|
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();
|
listPage.create();
|
||||||
editPage.createSecret(path, 'foo', 'bar');
|
editPage.createSecret(path, 'foo', 'bar');
|
||||||
andThen(() => {
|
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.equal(currentRouteName(), 'vault.cluster.secrets.backend.show', 'redirects to the show page');
|
||||||
assert.ok(showPage.editIsPresent, 'shows the edit button');
|
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]'),
|
path: fillable('[data-test-secret-backend-path]'),
|
||||||
submit: clickable('[data-test-secret-backend-submit]'),
|
submit: clickable('[data-test-secret-backend-submit]'),
|
||||||
toggleOptions: clickable('[data-test-secret-backend-options]'),
|
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]' }),
|
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]' }),
|
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]' }),
|
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
|
// Check if this is a root protected path
|
||||||
rootPath := c.router.RootPath(req.Path)
|
rootPath := c.router.RootPath(req.Path)
|
||||||
|
|
||||||
|
@ -1319,20 +1323,21 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
|
||||||
return 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
|
// Validate the token is a root token
|
||||||
acl, te, entity, err := c.fetchACLTokenEntryAndEntity(req.ClientToken)
|
acl, te, entity, err := c.fetchACLTokenEntryAndEntity(req.ClientToken)
|
||||||
if err != nil {
|
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)
|
retErr = multierror.Append(retErr, err)
|
||||||
c.stateLock.RUnlock()
|
c.stateLock.RUnlock()
|
||||||
return retErr
|
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
|
// Audit-log the request before going any further
|
||||||
auth := &logical.Auth{
|
auth := &logical.Auth{
|
||||||
ClientToken: req.ClientToken,
|
ClientToken: req.ClientToken,
|
||||||
Policies: te.Policies,
|
}
|
||||||
Metadata: te.Meta,
|
if te != nil {
|
||||||
DisplayName: te.DisplayName,
|
auth.Policies = te.Policies
|
||||||
EntityID: te.EntityID,
|
auth.Metadata = te.Meta
|
||||||
|
auth.DisplayName = te.DisplayName
|
||||||
|
auth.EntityID = te.EntityID
|
||||||
}
|
}
|
||||||
|
|
||||||
logInput := &audit.LogInput{
|
logInput := &audit.LogInput{
|
||||||
|
@ -1358,6 +1365,12 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
|
||||||
return 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)
|
// Attempt to use the token (decrement num_uses)
|
||||||
// On error bail out; if the token has been revoked, bail out too
|
// On error bail out; if the token has been revoked, bail out too
|
||||||
if te != nil {
|
if te != nil {
|
||||||
|
@ -1450,10 +1463,12 @@ func (c *Core) StepDown(req *logical.Request) (retErr error) {
|
||||||
// Audit-log the request before going any further
|
// Audit-log the request before going any further
|
||||||
auth := &logical.Auth{
|
auth := &logical.Auth{
|
||||||
ClientToken: req.ClientToken,
|
ClientToken: req.ClientToken,
|
||||||
Policies: te.Policies,
|
}
|
||||||
Metadata: te.Meta,
|
if te != nil {
|
||||||
DisplayName: te.DisplayName,
|
auth.Policies = te.Policies
|
||||||
EntityID: te.EntityID,
|
auth.Metadata = te.Meta
|
||||||
|
auth.DisplayName = te.DisplayName
|
||||||
|
auth.EntityID = te.EntityID
|
||||||
}
|
}
|
||||||
|
|
||||||
logInput := &audit.LogInput{
|
logInput := &audit.LogInput{
|
||||||
|
@ -1466,6 +1481,12 @@ func (c *Core) StepDown(req *logical.Request) (retErr error) {
|
||||||
return 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)
|
// Attempt to use the token (decrement num_uses)
|
||||||
if te != nil {
|
if te != nil {
|
||||||
te, err = c.tokenStore.UseToken(ctx, te)
|
te, err = c.tokenStore.UseToken(ctx, te)
|
||||||
|
|
|
@ -434,7 +434,7 @@ var aliasHelp = map[string][2]string{
|
||||||
"",
|
"",
|
||||||
},
|
},
|
||||||
"alias-id-list": {
|
"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,
|
Type: framework.TypeCommaStringSlice,
|
||||||
Description: "Policies to be tied to the entity.",
|
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{
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||||
logical.UpdateOperation: i.pathEntityRegister(),
|
logical.UpdateOperation: i.pathEntityRegister(),
|
||||||
|
@ -76,6 +80,10 @@ vault <command> <path> metadata=key1=value1 metadata=key2=value2
|
||||||
Type: framework.TypeCommaStringSlice,
|
Type: framework.TypeCommaStringSlice,
|
||||||
Description: "Policies to be tied to the entity.",
|
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{
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||||
logical.UpdateOperation: i.pathEntityIDUpdate(),
|
logical.UpdateOperation: i.pathEntityIDUpdate(),
|
||||||
|
@ -354,6 +362,11 @@ func (i *IdentityStore) handleEntityUpdateCommon(req *logical.Request, d *framew
|
||||||
entity.Policies = entityPoliciesRaw.([]string)
|
entity.Policies = entityPoliciesRaw.([]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disabledRaw, ok := d.GetOk("disabled")
|
||||||
|
if ok {
|
||||||
|
entity.Disabled = disabledRaw.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
// Get the name
|
// Get the name
|
||||||
entityName := d.Get("name").(string)
|
entityName := d.Get("name").(string)
|
||||||
if entityName != "" {
|
if entityName != "" {
|
||||||
|
@ -434,6 +447,7 @@ func (i *IdentityStore) handleEntityReadCommon(entity *identity.Entity) (*logica
|
||||||
respData["metadata"] = entity.Metadata
|
respData["metadata"] = entity.Metadata
|
||||||
respData["merged_entity_ids"] = entity.MergedEntityIDs
|
respData["merged_entity_ids"] = entity.MergedEntityIDs
|
||||||
respData["policies"] = entity.Policies
|
respData["policies"] = entity.Policies
|
||||||
|
respData["disabled"] = entity.Disabled
|
||||||
|
|
||||||
// Convert protobuf timestamp into RFC3339 format
|
// Convert protobuf timestamp into RFC3339 format
|
||||||
respData["creation_time"] = ptypes.TimestampString(entity.CreationTime)
|
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]),
|
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/?$",
|
Pattern: "group-alias/id/?$",
|
||||||
|
@ -76,8 +76,8 @@ func groupAliasPaths(i *IdentityStore) []*framework.Path {
|
||||||
logical.ListOperation: i.pathGroupAliasIDList(),
|
logical.ListOperation: i.pathGroupAliasIDList(),
|
||||||
},
|
},
|
||||||
|
|
||||||
HelpSynopsis: strings.TrimSpace(entityHelp["group-alias-id-list"][0]),
|
HelpSynopsis: strings.TrimSpace(groupAliasHelp["group-alias-id-list"][0]),
|
||||||
HelpDescription: strings.TrimSpace(entityHelp["group-alias-id-list"][1]),
|
HelpDescription: strings.TrimSpace(groupAliasHelp["group-alias-id-list"][1]),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -285,7 +285,7 @@ var groupAliasHelp = map[string][2]string{
|
||||||
"",
|
"",
|
||||||
},
|
},
|
||||||
"group-alias-id-list": {
|
"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(),
|
logical.ListOperation: i.pathGroupIDList(),
|
||||||
},
|
},
|
||||||
|
|
||||||
HelpSynopsis: strings.TrimSpace(entityHelp["group-id-list"][0]),
|
HelpSynopsis: strings.TrimSpace(groupHelp["group-id-list"][0]),
|
||||||
HelpDescription: strings.TrimSpace(entityHelp["group-id-list"][1]),
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mountAccessor := ""
|
||||||
|
if len(groupAliases) != 0 {
|
||||||
|
mountAccessor = groupAliases[0].MountAccessor
|
||||||
|
}
|
||||||
|
|
||||||
var newGroups []*identity.Group
|
var newGroups []*identity.Group
|
||||||
for _, alias := range groupAliases {
|
for _, alias := range groupAliases {
|
||||||
aliasByFactors, err := i.MemDBAliasByFactors(alias.MountAccessor, alias.Name, true, true)
|
aliasByFactors, err := i.MemDBAliasByFactors(alias.MountAccessor, alias.Name, true, true)
|
||||||
|
@ -2352,6 +2357,12 @@ func (i *IdentityStore) refreshExternalGroupMembershipsByEntityID(entityID strin
|
||||||
continue
|
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)
|
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)
|
group.MemberEntityIDs = strutil.StrListDelete(group.MemberEntityIDs, entityID)
|
||||||
|
|
|
@ -684,6 +684,9 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
|
||||||
logical.UpdateOperation: b.handlePolicySet,
|
logical.UpdateOperation: b.handlePolicySet,
|
||||||
logical.DeleteOperation: b.handlePolicyDelete,
|
logical.DeleteOperation: b.handlePolicyDelete,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
HelpSynopsis: strings.TrimSpace(sysHelp["policy"][0]),
|
||||||
|
HelpDescription: strings.TrimSpace(sysHelp["policy"][1]),
|
||||||
},
|
},
|
||||||
|
|
||||||
&framework.Path{
|
&framework.Path{
|
||||||
|
@ -1096,6 +1099,8 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
|
||||||
logical.DeleteOperation: b.handleRawDelete,
|
logical.DeleteOperation: b.handleRawDelete,
|
||||||
logical.ListOperation: b.handleRawList,
|
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": {
|
"passthrough_request_headers": {
|
||||||
"A list of headers to whitelist and pass from the request to the backend.",
|
"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
|
// return invalid request so that the status codes can be correct
|
||||||
errType := logical.ErrInvalidRequest
|
errType := logical.ErrInvalidRequest
|
||||||
switch ctErr {
|
switch ctErr {
|
||||||
case ErrInternalError, logical.ErrPermissionDenied:
|
case ErrInternalError, logical.ErrPermissionDenied, logical.ErrEntityDisabled:
|
||||||
errType = ctErr
|
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")
|
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
|
auth.EntityID = entity.ID
|
||||||
if auth.GroupAliases != nil {
|
if auth.GroupAliases != nil {
|
||||||
err = c.identityStore.refreshExternalGroupMembershipsByEntityID(auth.EntityID, auth.GroupAliases)
|
err = c.identityStore.refreshExternalGroupMembershipsByEntityID(auth.EntityID, auth.GroupAliases)
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/hashicorp/go-uuid"
|
"github.com/hashicorp/go-uuid"
|
||||||
"github.com/hashicorp/vault/helper/consts"
|
"github.com/hashicorp/vault/helper/consts"
|
||||||
|
"github.com/hashicorp/vault/helper/identity"
|
||||||
"github.com/hashicorp/vault/helper/jsonutil"
|
"github.com/hashicorp/vault/helper/jsonutil"
|
||||||
"github.com/hashicorp/vault/helper/locksutil"
|
"github.com/hashicorp/vault/helper/locksutil"
|
||||||
"github.com/hashicorp/vault/helper/parseutil"
|
"github.com/hashicorp/vault/helper/parseutil"
|
||||||
|
@ -104,6 +105,8 @@ type TokenStore struct {
|
||||||
salt *salt.Salt
|
salt *salt.Salt
|
||||||
|
|
||||||
tidyLock int64
|
tidyLock int64
|
||||||
|
|
||||||
|
identityPoliciesDeriverFunc func(string) (*identity.Entity, []string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTokenStore is used to construct a token store that is
|
// 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
|
// Initialize the store
|
||||||
t := &TokenStore{
|
t := &TokenStore{
|
||||||
view: view,
|
view: view,
|
||||||
cubbyholeDestroyer: destroyCubbyhole,
|
cubbyholeDestroyer: destroyCubbyhole,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tokenLocks: locksutil.CreateLocks(),
|
tokenLocks: locksutil.CreateLocks(),
|
||||||
saltLock: sync.RWMutex{},
|
saltLock: sync.RWMutex{},
|
||||||
|
identityPoliciesDeriverFunc: c.fetchEntityAndDerivedPolicies,
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.policyStore != nil {
|
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
|
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 {
|
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.`)
|
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
|
ServicePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *iamResourceImpl) SetIamPolicyRequest(p *Policy) (*http.Request, error) {
|
func (r *iamResourceImpl) SetIamPolicyRequest(p *Policy) (req *http.Request, err error) {
|
||||||
data := struct {
|
var data interface{}
|
||||||
Policy *Policy `json:"policy,omitempty"`
|
switch strings.ToLower(r.config.Service.Name) {
|
||||||
}{Policy: p}
|
// 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)
|
buf, err := googleapi.WithoutDataWrapper.JSONReader(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -240,6 +247,12 @@ func (r *iamResourceImpl) constructRequest(httpMtd *HttpMethodCfg, data io.Reade
|
||||||
return nil, err
|
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)
|
replacementMap := make(map[string]string)
|
||||||
for cId, replaceK := range httpMtd.ReplacementKeys {
|
for cId, replaceK := range httpMtd.ReplacementKeys {
|
||||||
rId, ok := r.relativeId.IdTuples[cId]
|
rId, ok := r.relativeId.IdTuples[cId]
|
||||||
|
|
|
@ -157,9 +157,9 @@ func (b *backend) serviceAccountPolicyRollback(ctx context.Context, req *logical
|
||||||
return err
|
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)
|
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]
|
currRoles, ok := rs.Bindings[entry.Resource]
|
||||||
if ok {
|
if ok {
|
||||||
rolesToRemove = rolesToRemove.Sub(currRoles)
|
rolesToRemove = rolesToRemove.Sub(currRoles)
|
||||||
|
|
|
@ -1315,22 +1315,22 @@
|
||||||
"revisionTime": "2018-04-03T19:54:48Z"
|
"revisionTime": "2018-04-03T19:54:48Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "0wzWab/JiPNjFvIx3yg5ztABJ7M=",
|
"checksumSHA1": "RPIFaxCjd6lchN99dcfhJ0vqqTI=",
|
||||||
"path": "github.com/hashicorp/vault-plugin-secrets-gcp/plugin",
|
"path": "github.com/hashicorp/vault-plugin-secrets-gcp/plugin",
|
||||||
"revision": "963249c4107fa592ba935ccdddd07155214a83bd",
|
"revision": "b5ca08100f178f8c3eb138c4f5c4a3d039ed173e",
|
||||||
"revisionTime": "2018-04-03T22:22:59Z"
|
"revisionTime": "2018-04-17T17:32:19Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "sV9w7yCitfGr2/4RjdKkdZjYAmA=",
|
"checksumSHA1": "1xWWeopXV4tbHTrVM1eFuH8TCuo=",
|
||||||
"path": "github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil",
|
"path": "github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil",
|
||||||
"revision": "963249c4107fa592ba935ccdddd07155214a83bd",
|
"revision": "b5ca08100f178f8c3eb138c4f5c4a3d039ed173e",
|
||||||
"revisionTime": "2018-04-03T22:22:59Z"
|
"revisionTime": "2018-04-17T17:32:19Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "81kYL49zTBoj1NYczxB2Xbr2d6Y=",
|
"checksumSHA1": "81kYL49zTBoj1NYczxB2Xbr2d6Y=",
|
||||||
"path": "github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util",
|
"path": "github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util",
|
||||||
"revision": "963249c4107fa592ba935ccdddd07155214a83bd",
|
"revision": "b5ca08100f178f8c3eb138c4f5c4a3d039ed173e",
|
||||||
"revisionTime": "2018-04-03T22:22:59Z"
|
"revisionTime": "2018-04-17T17:32:19Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "m3cQgQrCSuWHiPA339FaZU6LuHU=",
|
"checksumSHA1": "m3cQgQrCSuWHiPA339FaZU6LuHU=",
|
||||||
|
|
|
@ -662,8 +662,7 @@ list in order to satisfy that constraint.
|
||||||
- `period` `(string: "")` - If set, indicates that the token generated using
|
- `period` `(string: "")` - If set, indicates that the token generated using
|
||||||
this role should never expire. The token should be renewed within the duration
|
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
|
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
|
value of this parameter.
|
||||||
this role.
|
|
||||||
- `policies` `(array: [])` - Policies to be set on tokens issued using this
|
- `policies` `(array: [])` - Policies to be set on tokens issued using this
|
||||||
role.
|
role.
|
||||||
- `allow_instance_migration` `(bool: false)` - If set, allows migration of the
|
- `allow_instance_migration` `(bool: false)` - If set, allows migration of the
|
||||||
|
|
|
@ -180,18 +180,30 @@ $ curl \
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"id": "ClientToken",
|
"accessor": "8609694a-cdbc-db9b-d345-e782dbb562ed",
|
||||||
"policies": [
|
"creation_time": 1523979354,
|
||||||
"web",
|
"creation_ttl": 2764800,
|
||||||
"stage"
|
"display_name": "ldap2-tesla",
|
||||||
],
|
"entity_id": "7d2e3179-f69b-450c-7179-ac8ee8bd8ca9",
|
||||||
"path": "auth/github/login",
|
"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": {
|
"meta": {
|
||||||
"user": "armon",
|
"username": "tesla"
|
||||||
"organization": "hashicorp"
|
|
||||||
},
|
},
|
||||||
"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
|
```json
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"id": "ClientToken",
|
"accessor": "8609694a-cdbc-db9b-d345-e782dbb562ed",
|
||||||
"policies": [
|
"creation_time": 1523979354,
|
||||||
"web",
|
"creation_ttl": 2764800,
|
||||||
"stage"
|
"display_name": "ldap2-tesla",
|
||||||
],
|
"entity_id": "7d2e3179-f69b-450c-7179-ac8ee8bd8ca9",
|
||||||
"path": "auth/github/login",
|
"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": {
|
"meta": {
|
||||||
"user": "armon",
|
"username": "tesla"
|
||||||
"organization": "hashicorp"
|
|
||||||
},
|
},
|
||||||
"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
|
```json
|
||||||
{
|
{
|
||||||
"accessor": "2c84f488-2133-4ced-87b0-570f93a76830"
|
"accessor": "8609694a-cdbc-db9b-d345-e782dbb562ed"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -267,25 +291,32 @@ $ curl \
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"lease_id": "",
|
|
||||||
"renewable": false,
|
|
||||||
"lease_duration": 0,
|
|
||||||
"data": {
|
"data": {
|
||||||
"creation_time": 1457533232,
|
"accessor": "8609694a-cdbc-db9b-d345-e782dbb562ed",
|
||||||
|
"creation_time": 1523979354,
|
||||||
"creation_ttl": 2764800,
|
"creation_ttl": 2764800,
|
||||||
"display_name": "token",
|
"display_name": "ldap2-tesla",
|
||||||
"meta": null,
|
"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,
|
"num_uses": 0,
|
||||||
"orphan": false,
|
"orphan": true,
|
||||||
"path": "auth/token/create",
|
"path": "auth/ldap2/login/tesla",
|
||||||
"policies": [
|
"policies": [
|
||||||
"default",
|
"default",
|
||||||
"web"
|
"testgroup2-policy"
|
||||||
],
|
],
|
||||||
"ttl": 2591976
|
"renewable": true,
|
||||||
},
|
"ttl": 2763902
|
||||||
"warnings": null,
|
}
|
||||||
"auth": null
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,9 @@ This endpoint creates or updates an Entity.
|
||||||
|
|
||||||
- `policies` `(list of strings: [])` – Policies to be tied to the 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
|
### Sample Payload
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
@ -86,6 +89,7 @@ $ curl \
|
||||||
"data": {
|
"data": {
|
||||||
"bucket_key_hash": "177553e4c58987f4cc5d7e530136c642",
|
"bucket_key_hash": "177553e4c58987f4cc5d7e530136c642",
|
||||||
"creation_time": "2017-07-25T20:29:22.614756844Z",
|
"creation_time": "2017-07-25T20:29:22.614756844Z",
|
||||||
|
"disabled": false,
|
||||||
"id": "8d6a45e5-572f-8f13-d226-cd0d1ec57297",
|
"id": "8d6a45e5-572f-8f13-d226-cd0d1ec57297",
|
||||||
"last_update_time": "2017-07-25T20:29:22.614756844Z",
|
"last_update_time": "2017-07-25T20:29:22.614756844Z",
|
||||||
"metadata": {
|
"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.
|
- `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
|
### Sample Payload
|
||||||
|
|
||||||
|
|
|
@ -222,7 +222,7 @@ can be achieved without `sudo` via `sys/mounts/auth/[auth-path]/tune`._
|
||||||
object.
|
object.
|
||||||
|
|
||||||
- `listing_visibility` `(string: "")` - Speficies whether to show this mount
|
- `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
|
- `passthrough_request_headers` `(array: [])` - Comma-separated list of headers
|
||||||
to whitelist and pass from the request to the backend.
|
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.
|
object.
|
||||||
|
|
||||||
- `listing_visibility` `(string: "")` - Speficies whether to show this mount
|
- `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
|
- `passthrough_request_headers` `(array: [])` - Comma-separated list of headers
|
||||||
to whitelist and pass from the request to the backend.
|
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.
|
correct parameters to connect to the host.
|
||||||
|
|
||||||
```text
|
```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`
|
OTP for the session is `b4d47e1b-4879-5f4e-ce5c-7988d7986f37`
|
||||||
[Note: Install `sshpass` to automate typing in OTP]
|
[Note: Install `sshpass` to automate typing in OTP]
|
||||||
Password: <Enter OTP>
|
Password: <Enter OTP>
|
||||||
|
@ -101,7 +101,7 @@ Password: <Enter OTP>
|
||||||
The OTP will be entered automatically using `sshpass` if it is installed.
|
The OTP will be entered automatically using `sshpass` if it is installed.
|
||||||
|
|
||||||
```text
|
```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>:~$
|
username@<IP of remote host>:~$
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ $ vault kv put secret/hello foo=world excited=yes
|
||||||
Success! Data written to: secret/hello
|
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
|
directly from the command-line, it can read values and key pairs from
|
||||||
`STDIN` as well as files. For more information, see the
|
`STDIN` as well as files. For more information, see the
|
||||||
[command documentation](/docs/commands/index.html).
|
[command documentation](/docs/commands/index.html).
|
||||||
|
|
|
@ -219,6 +219,9 @@
|
||||||
<li<%= sidebar_current("docs-http-system-init") %>>
|
<li<%= sidebar_current("docs-http-system-init") %>>
|
||||||
<a href="/api/system/init.html"><tt>/sys/init</tt></a>
|
<a href="/api/system/init.html"><tt>/sys/init</tt></a>
|
||||||
</li>
|
</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") %>>
|
<li<%= sidebar_current("docs-http-system-key-status") %>>
|
||||||
<a href="/api/system/key-status.html"><tt>/sys/key-status</tt></a>
|
<a href="/api/system/key-status.html"><tt>/sys/key-status</tt></a>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in New Issue