Merge branch 'master' into jo-ie11-fixes

This commit is contained in:
Joshua Ogle 2018-04-17 14:07:05 -06:00 committed by GitHub
commit 7bb9490403
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 1165 additions and 288 deletions

View File

@ -1,3 +1,20 @@
## 0.10.1 (Unreleased)
IMPROVEMENTS:
* identity: Add the ability to disable an entity. Disabling an entity does not
revoke associated tokens, but while the entity is disabled they cannot be
used. [GH-4353]
* auth/token: Add to the token lookup response, the policies inherited due to
identity associations [GH-4366]
BUG FIXES:
* identity: Persist entity memberships in external identity groups across
mounts [GH-4365]
* secret/gcp: Fix panic on rollback when a roleset wasn't created properly
[GH-4344]
## 0.10.0 (April 10th, 2018)
SECURITY:

View File

@ -606,7 +606,7 @@ CLUSTER_SYNTHESIS_COMPLETE:
// Force https as we'll always be TLS-secured
u, err := url.ParseRequestURI(coreConfig.ClusterAddr)
if err != nil {
c.UI.Error(fmt.Sprintf("Error parsing cluster address %s: %v", coreConfig.RedirectAddr, err))
c.UI.Error(fmt.Sprintf("Error parsing cluster address %s: %v", coreConfig.ClusterAddr, err))
return 11
}
u.Scheme = "https"

View File

@ -200,6 +200,9 @@ type Entity struct {
// the entities belonging to a particular bucket during invalidation of the
// storage key.
BucketKeyHash string `sentinel:"" protobuf:"bytes,9,opt,name=bucket_key_hash,json=bucketKeyHash" json:"bucket_key_hash,omitempty"`
// Disabled indicates whether tokens associated with the account should not
// be able to be used
Disabled bool `sentinel:"" protobuf:"varint,11,opt,name=disabled" json:"disabled,omitempty"`
}
func (m *Entity) Reset() { *m = Entity{} }
@ -270,6 +273,13 @@ func (m *Entity) GetBucketKeyHash() string {
return ""
}
func (m *Entity) GetDisabled() bool {
if m != nil {
return m.Disabled
}
return false
}
// Alias represents the alias that gets stored inside of the
// entity object in storage and also represents in an in-memory index of an
// alias object.
@ -392,43 +402,44 @@ func init() {
func init() { proto.RegisterFile("types.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 603 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x93, 0xdd, 0x6e, 0xd3, 0x30,
0x14, 0xc7, 0xd5, 0xa6, 0x9f, 0x27, 0x5d, 0x37, 0x2c, 0x84, 0x4c, 0xa5, 0x41, 0x37, 0x69, 0x28,
0x70, 0x91, 0x49, 0xe3, 0x86, 0x8d, 0x0b, 0x34, 0xc1, 0x80, 0x09, 0x21, 0xa1, 0x68, 0x5c, 0x47,
0x6e, 0xe2, 0xb5, 0xd6, 0x92, 0x38, 0x8a, 0x1d, 0x44, 0x5e, 0x87, 0x97, 0xe1, 0x69, 0x78, 0x07,
0xe4, 0xe3, 0xa6, 0x0d, 0x74, 0x7c, 0x4c, 0xdb, 0x9d, 0xf3, 0x3f, 0xc7, 0xc7, 0x27, 0xe7, 0xff,
0x3b, 0xe0, 0xea, 0x2a, 0xe7, 0xca, 0xcf, 0x0b, 0xa9, 0x25, 0x19, 0x88, 0x98, 0x67, 0x5a, 0xe8,
0x6a, 0xf2, 0x78, 0x2e, 0xe5, 0x3c, 0xe1, 0x87, 0xa8, 0xcf, 0xca, 0xcb, 0x43, 0x2d, 0x52, 0xae,
0x34, 0x4b, 0x73, 0x9b, 0xba, 0xff, 0xad, 0x03, 0xdd, 0x77, 0x85, 0x2c, 0x73, 0x32, 0x86, 0xb6,
0x88, 0x69, 0x6b, 0xda, 0xf2, 0x86, 0x41, 0x5b, 0xc4, 0x84, 0x40, 0x27, 0x63, 0x29, 0xa7, 0x6d,
0x54, 0xf0, 0x4c, 0x26, 0x30, 0xc8, 0x65, 0x22, 0x22, 0xc1, 0x15, 0x75, 0xa6, 0x8e, 0x37, 0x0c,
0x56, 0xdf, 0xc4, 0x83, 0x9d, 0x9c, 0x15, 0x3c, 0xd3, 0xe1, 0xdc, 0xd4, 0x0b, 0x45, 0xac, 0x68,
0x07, 0x73, 0xc6, 0x56, 0xc7, 0x67, 0xce, 0x63, 0x45, 0x9e, 0xc1, 0xbd, 0x94, 0xa7, 0x33, 0x5e,
0x84, 0xb6, 0x4b, 0x4c, 0xed, 0x62, 0xea, 0xb6, 0x0d, 0x9c, 0xa1, 0x6e, 0x72, 0x8f, 0x61, 0x90,
0x72, 0xcd, 0x62, 0xa6, 0x19, 0xed, 0x4d, 0x1d, 0xcf, 0x3d, 0xda, 0xf5, 0xeb, 0xbf, 0xf3, 0xb1,
0xa2, 0xff, 0x71, 0x19, 0x3f, 0xcb, 0x74, 0x51, 0x05, 0xab, 0x74, 0xf2, 0x0a, 0xb6, 0xa2, 0x82,
0x33, 0x2d, 0x64, 0x16, 0x9a, 0xdf, 0xa6, 0xfd, 0x69, 0xcb, 0x73, 0x8f, 0x26, 0xbe, 0x9d, 0x89,
0x5f, 0xcf, 0xc4, 0xbf, 0xa8, 0x67, 0x12, 0x8c, 0xea, 0x0b, 0x46, 0x22, 0x6f, 0x60, 0x27, 0x61,
0x4a, 0x87, 0x65, 0x1e, 0x33, 0xcd, 0x6d, 0x8d, 0xc1, 0x3f, 0x6b, 0x8c, 0xcd, 0x9d, 0xcf, 0x78,
0x05, 0xab, 0xec, 0xc1, 0x28, 0x95, 0xb1, 0xb8, 0xac, 0x42, 0x91, 0xc5, 0xfc, 0x2b, 0x1d, 0x4e,
0x5b, 0x5e, 0x27, 0x70, 0xad, 0x76, 0x6e, 0x24, 0xf2, 0x04, 0xb6, 0x67, 0x65, 0x74, 0xc5, 0x75,
0x78, 0xc5, 0xab, 0x70, 0xc1, 0xd4, 0x82, 0x02, 0x4e, 0x7d, 0xcb, 0xca, 0x1f, 0x78, 0xf5, 0x9e,
0xa9, 0x05, 0x39, 0x80, 0x2e, 0x4b, 0x04, 0x53, 0xd4, 0xc5, 0x2e, 0xb6, 0xd7, 0x93, 0x38, 0x35,
0x72, 0x60, 0xa3, 0xc6, 0x39, 0x43, 0x03, 0x1d, 0x59, 0xe7, 0xcc, 0x79, 0xf2, 0x12, 0xb6, 0x7e,
0x99, 0x13, 0xd9, 0x01, 0xe7, 0x8a, 0x57, 0x4b, 0xbf, 0xcd, 0x91, 0xdc, 0x87, 0xee, 0x17, 0x96,
0x94, 0xb5, 0xe3, 0xf6, 0xe3, 0xa4, 0xfd, 0xa2, 0xb5, 0xff, 0xdd, 0x81, 0x9e, 0xb5, 0x84, 0x3c,
0x85, 0x3e, 0x3e, 0xc2, 0x15, 0x6d, 0xa1, 0x1d, 0x1b, 0x4d, 0xd4, 0xf1, 0x25, 0x50, 0xed, 0x0d,
0xa0, 0x9c, 0x06, 0x50, 0x27, 0x0d, 0x7b, 0x3b, 0x58, 0xef, 0xd1, 0xba, 0x9e, 0x7d, 0xf2, 0xff,
0xfd, 0xed, 0xde, 0x81, 0xbf, 0xbd, 0x1b, 0xfb, 0x8b, 0x34, 0x17, 0x73, 0x1e, 0x37, 0x69, 0xee,
0xd7, 0x34, 0x9b, 0xc0, 0x9a, 0xe6, 0xe6, 0xfe, 0x0c, 0x7e, 0xdb, 0x9f, 0x6b, 0x20, 0x18, 0x5e,
0x03, 0xc1, 0xed, 0x9c, 0xfc, 0xe1, 0x40, 0x17, 0x6d, 0xda, 0x58, 0xf7, 0x3d, 0x18, 0x45, 0x2c,
0x93, 0x99, 0x88, 0x58, 0x12, 0xae, 0x7c, 0x73, 0x57, 0xda, 0x79, 0x4c, 0x76, 0x01, 0x52, 0x59,
0x66, 0x3a, 0x44, 0xba, 0xac, 0x8d, 0x43, 0x54, 0x2e, 0xaa, 0x9c, 0x93, 0x03, 0x18, 0xdb, 0x30,
0x8b, 0x22, 0xae, 0x94, 0x2c, 0x68, 0xc7, 0xf6, 0x8f, 0xea, 0xe9, 0x52, 0x5c, 0x57, 0xc9, 0x99,
0x5e, 0xa0, 0x67, 0x75, 0x95, 0x4f, 0x4c, 0x2f, 0xfe, 0xbe, 0xf0, 0xd8, 0xfa, 0x1f, 0x81, 0xa8,
0x01, 0xeb, 0x37, 0x00, 0xdb, 0x80, 0x64, 0x70, 0x07, 0x90, 0x0c, 0x6f, 0x0c, 0xc9, 0x31, 0x3c,
0x5c, 0x42, 0x72, 0x59, 0xc8, 0x34, 0x6c, 0x4e, 0x5a, 0x51, 0x40, 0x12, 0x1e, 0xd8, 0x84, 0xb7,
0x85, 0x4c, 0x5f, 0xaf, 0x87, 0xae, 0x6e, 0xe5, 0xf7, 0xac, 0x87, 0xbd, 0x3d, 0xff, 0x19, 0x00,
0x00, 0xff, 0xff, 0x8e, 0x4a, 0xc5, 0xdb, 0x1f, 0x06, 0x00, 0x00,
// 617 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x94, 0xdd, 0x6e, 0xd3, 0x30,
0x14, 0xc7, 0xd5, 0xa6, 0x1f, 0xe9, 0x69, 0xd7, 0x0d, 0x0b, 0x21, 0x53, 0x69, 0xd0, 0x4d, 0x1a,
0x2a, 0x5c, 0x64, 0xd2, 0xb8, 0x61, 0xe3, 0x02, 0x4d, 0x30, 0x60, 0x42, 0x48, 0x28, 0x1a, 0xd7,
0x91, 0x1b, 0x7b, 0xad, 0xb5, 0x24, 0x8e, 0x62, 0x17, 0x91, 0xd7, 0xe1, 0xd5, 0xb8, 0xe6, 0x1d,
0x90, 0x8f, 0x9b, 0x36, 0xd0, 0xf1, 0x31, 0x6d, 0x77, 0xf6, 0xff, 0x1c, 0x1f, 0x1f, 0x9f, 0xff,
0x2f, 0x81, 0xbe, 0x29, 0x73, 0xa1, 0x83, 0xbc, 0x50, 0x46, 0x11, 0x5f, 0x72, 0x91, 0x19, 0x69,
0xca, 0xd1, 0xe3, 0x99, 0x52, 0xb3, 0x44, 0x1c, 0xa2, 0x3e, 0x5d, 0x5c, 0x1e, 0x1a, 0x99, 0x0a,
0x6d, 0x58, 0x9a, 0xbb, 0xd4, 0xfd, 0x6f, 0x2d, 0x68, 0xbf, 0x2b, 0xd4, 0x22, 0x27, 0x43, 0x68,
0x4a, 0x4e, 0x1b, 0xe3, 0xc6, 0xa4, 0x17, 0x36, 0x25, 0x27, 0x04, 0x5a, 0x19, 0x4b, 0x05, 0x6d,
0xa2, 0x82, 0x6b, 0x32, 0x02, 0x3f, 0x57, 0x89, 0x8c, 0xa5, 0xd0, 0xd4, 0x1b, 0x7b, 0x93, 0x5e,
0xb8, 0xda, 0x93, 0x09, 0xec, 0xe4, 0xac, 0x10, 0x99, 0x89, 0x66, 0xb6, 0x5e, 0x24, 0xb9, 0xa6,
0x2d, 0xcc, 0x19, 0x3a, 0x1d, 0xaf, 0x39, 0xe7, 0x9a, 0x3c, 0x83, 0x7b, 0xa9, 0x48, 0xa7, 0xa2,
0x88, 0x5c, 0x97, 0x98, 0xda, 0xc6, 0xd4, 0x6d, 0x17, 0x38, 0x43, 0xdd, 0xe6, 0x1e, 0x83, 0x9f,
0x0a, 0xc3, 0x38, 0x33, 0x8c, 0x76, 0xc6, 0xde, 0xa4, 0x7f, 0xb4, 0x1b, 0x54, 0xaf, 0x0b, 0xb0,
0x62, 0xf0, 0x71, 0x19, 0x3f, 0xcb, 0x4c, 0x51, 0x86, 0xab, 0x74, 0xf2, 0x0a, 0xb6, 0xe2, 0x42,
0x30, 0x23, 0x55, 0x16, 0xd9, 0x67, 0xd3, 0xee, 0xb8, 0x31, 0xe9, 0x1f, 0x8d, 0x02, 0x37, 0x93,
0xa0, 0x9a, 0x49, 0x70, 0x51, 0xcd, 0x24, 0x1c, 0x54, 0x07, 0xac, 0x44, 0xde, 0xc0, 0x4e, 0xc2,
0xb4, 0x89, 0x16, 0x39, 0x67, 0x46, 0xb8, 0x1a, 0xfe, 0x3f, 0x6b, 0x0c, 0xed, 0x99, 0xcf, 0x78,
0x04, 0xab, 0xec, 0xc1, 0x20, 0x55, 0x5c, 0x5e, 0x96, 0x91, 0xcc, 0xb8, 0xf8, 0x4a, 0x7b, 0xe3,
0xc6, 0xa4, 0x15, 0xf6, 0x9d, 0x76, 0x6e, 0x25, 0xf2, 0x04, 0xb6, 0xa7, 0x8b, 0xf8, 0x4a, 0x98,
0xe8, 0x4a, 0x94, 0xd1, 0x9c, 0xe9, 0x39, 0x05, 0x9c, 0xfa, 0x96, 0x93, 0x3f, 0x88, 0xf2, 0x3d,
0xd3, 0x73, 0x72, 0x00, 0x6d, 0x96, 0x48, 0xa6, 0x69, 0x1f, 0xbb, 0xd8, 0x5e, 0x4f, 0xe2, 0xd4,
0xca, 0xa1, 0x8b, 0x5a, 0xe7, 0x2c, 0x0d, 0x74, 0xe0, 0x9c, 0xb3, 0xeb, 0xd1, 0x4b, 0xd8, 0xfa,
0x65, 0x4e, 0x64, 0x07, 0xbc, 0x2b, 0x51, 0x2e, 0xfd, 0xb6, 0x4b, 0x72, 0x1f, 0xda, 0x5f, 0x58,
0xb2, 0xa8, 0x1c, 0x77, 0x9b, 0x93, 0xe6, 0x8b, 0xc6, 0xfe, 0x77, 0x0f, 0x3a, 0xce, 0x12, 0xf2,
0x14, 0xba, 0x78, 0x89, 0xd0, 0xb4, 0x81, 0x76, 0x6c, 0x34, 0x51, 0xc5, 0x97, 0x40, 0x35, 0x37,
0x80, 0xf2, 0x6a, 0x40, 0x9d, 0xd4, 0xec, 0x6d, 0x61, 0xbd, 0x47, 0xeb, 0x7a, 0xee, 0xca, 0xff,
0xf7, 0xb7, 0x7d, 0x07, 0xfe, 0x76, 0x6e, 0xec, 0x2f, 0xd2, 0x5c, 0xcc, 0x04, 0xaf, 0xd3, 0xdc,
0xad, 0x68, 0xb6, 0x81, 0x35, 0xcd, 0xf5, 0xef, 0xc7, 0xff, 0xed, 0xfb, 0xb9, 0x06, 0x82, 0xde,
0x75, 0x10, 0x8c, 0xc0, 0xe7, 0x52, 0xb3, 0x69, 0x22, 0x38, 0x72, 0xe0, 0x87, 0xab, 0xfd, 0xed,
0x5c, 0xfe, 0xe1, 0x41, 0x1b, 0x2d, 0xdc, 0xf8, 0x15, 0xec, 0xc1, 0x20, 0x66, 0x99, 0xca, 0x64,
0xcc, 0x92, 0x68, 0xe5, 0x69, 0x7f, 0xa5, 0x9d, 0x73, 0xb2, 0x0b, 0x90, 0xaa, 0x45, 0x66, 0x22,
0x24, 0xcf, 0x59, 0xdc, 0x43, 0xe5, 0xa2, 0xcc, 0x05, 0x39, 0x80, 0xa1, 0x0b, 0xb3, 0x38, 0x16,
0x5a, 0xab, 0x82, 0xb6, 0xdc, 0xdb, 0x50, 0x3d, 0x5d, 0x8a, 0xeb, 0x2a, 0x39, 0x33, 0x73, 0xf4,
0xb3, 0xaa, 0xf2, 0x89, 0x99, 0xf9, 0xdf, 0x7f, 0x06, 0xd8, 0xfa, 0x1f, 0x61, 0xa9, 0xe0, 0xeb,
0xd6, 0xe0, 0xdb, 0x00, 0xc8, 0xbf, 0x03, 0x80, 0x7a, 0x37, 0x06, 0xe8, 0x18, 0x1e, 0x2e, 0x01,
0xba, 0x2c, 0x54, 0x1a, 0xd5, 0x27, 0xad, 0x29, 0x20, 0x25, 0x0f, 0x5c, 0xc2, 0xdb, 0x42, 0xa5,
0xaf, 0xd7, 0x43, 0xd7, 0xb7, 0xf2, 0x7b, 0xda, 0xc1, 0xde, 0x9e, 0xff, 0x0c, 0x00, 0x00, 0xff,
0xff, 0x60, 0x0b, 0xc9, 0x74, 0x3b, 0x06, 0x00, 0x00,
}

View File

@ -110,6 +110,10 @@ message Entity {
// MFASecrets holds the MFA secrets indexed by the identifier of the MFA
// method configuration.
//map<string, mfa.Secret> mfa_secrets = 10;
// Disabled indicates whether tokens associated with the account should not
// be able to be used
bool disabled = 11;
}
// Alias represents the alias that gets stored inside of the

View File

@ -273,6 +273,10 @@ var (
// ErrPermissionDenied is returned if the client is not authorized
ErrPermissionDenied = errors.New("permission denied")
// ErrDisabledEntity is returned if the entity tied to a token is marked as
// disabled
ErrEntityDisabled = errors.New("entity associated with token is disabled")
// ErrMultiAuthzPending is returned if the the request needs more
// authorizations
ErrMultiAuthzPending = errors.New("request needs further approval")

View File

@ -12,17 +12,37 @@ export default Ember.Component.extend({
return `${this.get('filename')}-${new Date().toISOString()}.${this.get('extension')}`;
}),
href: computed('data', 'mime', 'stringify', function() {
fileLike: computed('data', 'mime', 'strigify', 'download', function() {
let file;
let data = this.get('data');
const mime = this.get('mime');
let filename = this.get('download');
let mime = this.get('mime');
if (this.get('stringify')) {
data = JSON.stringify(data, null, 2);
}
const file = new File([data], { type: mime });
return window.URL.createObjectURL(file);
if (window.navigator.msSaveOrOpenBlob) {
file = new Blob([data], { type: mime });
file.name = filename;
} else {
file = new File([data], filename, { type: mime });
}
return file;
}),
href: computed('fileLike', function() {
return window.URL.createObjectURL(this.get('fileLike'));
}),
click(event) {
if (!window.navigator.msSaveOrOpenBlob) {
return;
}
event.preventDefault();
let file = this.get('fileLike');
//lol whyyyy
window.navigator.msSaveOrOpenBlob(file, file.name);
},
actionText: 'Download',
data: null,
filename: null,

View File

@ -8,13 +8,13 @@ const { computed } = Ember;
export default Ember.Controller.extend({
mountTypes: [
{ label: 'AWS', value: 'aws' },
{ label: 'Cassandra', value: 'cassandra' },
{ label: 'Cassandra', value: 'cassandra', deprecated: true },
{ label: 'Consul', value: 'consul' },
{ label: 'Databases', value: 'database' },
{ label: 'Google Cloud', value: 'gcp' },
{ label: 'KV', value: 'kv' },
{ label: 'MongoDB', value: 'mongodb' },
{ label: 'MS SQL', value: 'mssql', deprecated: true },
{ label: 'MongoDB', value: 'mongodb', deprecated: true },
{ label: 'MSSQL', value: 'mssql', deprecated: true },
{ label: 'MySQL', value: 'mysql', deprecated: true },
{ label: 'Nomad', value: 'nomad' },
{ label: 'PKI', value: 'pki' },

View File

@ -46,7 +46,7 @@ export default EditBase.extend({
const parentKey = params.secret ? params.secret : '';
return Ember.RSVP.hash({
secret: this.createModel(transition, parentKey),
capabilities: this.capabilities(parentKey),
capabilities: {},
});
},
});

View File

@ -27,12 +27,6 @@ export default Ember.Route.extend({
}
},
capabilities(secret) {
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
const path = backend + '/' + secret;
return this.store.findRecord('capabilities', path);
},
getModelType(backend, tab) {
const types = {
transit: 'transit-key',
@ -49,8 +43,7 @@ export default Ember.Route.extend({
const secret = params.secret ? params.secret : '';
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
const backends = this.modelFor('vault.cluster.secrets').mapBy('id');
return Ember.RSVP.hash({
secrets: this.store
return this.store
.lazyPaginatedQuery(this.getModelType(backend, params.tab), {
id: secret,
backend,
@ -69,8 +62,6 @@ export default Ember.Route.extend({
} else {
throw err;
}
}),
capabilities: this.capabilities(secret),
});
},
@ -107,8 +98,7 @@ export default Ember.Route.extend({
const has404 = this.get('has404');
controller.set('hasModel', true);
controller.setProperties({
model: model.secrets,
capabilities: model.capabilities,
model,
baseKey: { id: secret },
has404,
backend,
@ -125,7 +115,7 @@ export default Ember.Route.extend({
}
controller.setProperties({
filter: filter || '',
page: model.secrets.get('meta.currentPage') || 1,
page: model.get('meta.currentPage') || 1,
});
}
},

View File

@ -5,11 +5,16 @@ import UnloadModelRoute from 'vault/mixins/unload-model-route';
export default Ember.Route.extend(UnloadModelRoute, {
capabilities(secret) {
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
let backendModel = this.store.peekRecord('secret-engine', backend);
let backendType = backendModel.get('type');
let version = backendModel.get('options.version');
let path;
if (backend === 'transit') {
if (backendType === 'transit') {
path = backend + '/keys/' + secret;
} else if (backend === 'ssh' || backend === 'aws') {
} else if (backendType === 'ssh' || backendType === 'aws') {
path = backend + '/roles/' + secret;
} else if (version && version === 2) {
path = backend + '/data/' + secret;
} else {
path = backend + '/' + secret;
}

View File

@ -52,7 +52,6 @@
</div>
<div class="field is-grouped-split box is-fullwidth is-bottomless">
<div class="control">
{{#if capabilities.canCreate}}
<button
type="submit"
disabled={{buttonDisabled}}
@ -65,7 +64,6 @@
Save
{{/if}}
</button>
{{/if}}
{{#secret-link
mode=(if (eq mode "create") "list" "show")
class="button"

View File

@ -5,7 +5,6 @@
</div>
<div class="field is-grouped-split box is-fullwidth is-bottomless">
<div class="control">
{{#if capabilities.canCreate}}
<button
type="submit"
disabled={{buttonDisabled}}
@ -18,7 +17,6 @@
Save
{{/if}}
</button>
{{/if}}
{{#secret-link
mode=(if (eq mode "create") "list" "show")
class="button"

View File

@ -25,7 +25,6 @@
</div>
<div class="field is-grouped-split box is-fullwidth is-bottomless">
<div class="control">
{{#if capabilities.canCreate}}
<button
type="submit"
disabled={{buttonDisabled}}
@ -38,7 +37,6 @@
Save
{{/if}}
</button>
{{/if}}
{{#secret-link
mode=(if (eq mode "create") "list" "show")
class="button"

View File

@ -34,7 +34,6 @@
<div class="field is-grouped box is-fullwidth is-bottomless">
<div class="control">
{{#if capabilities.canCreate}}
<button
type="submit"
disabled={{buttonDisabled}}
@ -43,7 +42,6 @@
>
Save
</button>
{{/if}}
</div>
<div class="control">
{{#secret-link

View File

@ -80,11 +80,11 @@
<div class="field">
<div class="b-checkbox">
<input type="checkbox"
data-test-transit-key-convergent-encryption
id="key-convergent"
class="styled"
checked={{key.convergentEncryption}}
onchange={{action "convergentEncryptionChange" value="target.checked"}}
data-test-transit-key-convergent-encryption=true
/>
<label for="key-convergent" class="is-label">
Enable convergent encryption
@ -94,18 +94,16 @@
{{/if}}
</div>
<div class="field is-grouped box is-fullwidth is-bottomless">
{{#if capabilities.canCreate}}
<div class="control">
<button
data-test-transit-key-create
type="submit"
disabled={{requestInFlight}}
class="button is-primary {{if requestInFlight 'is-loading'}}"
data-test-transit-key-create=true
>
Create encryption key
</button>
</div>
{{/if}}
<div class="control">
{{#secret-link
mode="list"

View File

@ -20,7 +20,7 @@
</h1>
</div>
<div class="level-right field is-grouped">
{{#if (and (not-eq tab "certs") capabilities.canCreate)}}
{{#if (not-eq tab "certs")}}
<div class="control">
{{#secret-link
mode="create"
@ -114,8 +114,7 @@
{{else}}
<div class="box is-sideless">
{{#if filterFocused}}
There are no {{pluralize options.item}} matching <code>{{filter}}</code>
{{#if capabilities.canCreate}}, press <kbd>ENTER</kbd> to add one{{/if}}.
There are no {{pluralize options.item}} matching <code>{{filter}}</code>, press <kbd>ENTER</kbd> to add one.
{{else}}
There are no {{pluralize options.item}} matching <code>{{filter}}</code>.
{{/if}}

View File

@ -28,7 +28,7 @@
<div class="level-left">
<div>
<a data-test-secret-path href={{href-to 'vault.cluster.secrets.backend.list-root' backend.id}} class="has-text-black has-text-weight-semibold">
{{i-con glyph="folder" size=14 class="has-text-grey-light"}} {{backend.path}}
{{i-con glyph="folder" size=14 class="has-text-grey-light"}}{{backend.path}}
</a>
<br />
<span class="tag">

View File

@ -56,10 +56,10 @@
<div class="control is-expanded">
<div class="select is-fullwidth">
<select
data-test-secret-backend-version
id="backend-type"
value={{selectedType}}
onchange={{action (mut version) value="target.value"}}
data-test-secret-backend-type=true
>
{{#each (array 1 2) as |versionOption|}}
<option selected={{eq version versionOption}} value={{versionOption}}>

View File

@ -5,8 +5,10 @@ if(process.argv[2]){
process.exit(0);
}
process.env.TERM = 'dumb';
var fs = require('fs');
var path = require('path');
var readline = require('readline')
var spawn = require('child_process').spawn;
var vault = spawn(
'vault',
@ -21,21 +23,15 @@ var vault = spawn(
]
);
// https://github.com/chalk/ansi-regex/blob/master/index.js
var ansiPattern = [
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)',
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))'
].join('|');
var ANSI_REGEX = new RegExp(ansiPattern, 'g');
var output = '';
var unseal, root;
vault.stdout.on('data', function(data) {
var stringData = data.toString().replace(ANSI_REGEX, '');
output = output + stringData;
console.log(stringData);
readline.createInterface({
input : vault.stdout,
terminal : false
}).on('line', function(line) {
output = output + line;
console.log(line);
var unsealMatch = output.match(/Unseal Key\: (.+)$/m);
if (unsealMatch && !unseal) { unseal = unsealMatch[1] };
var rootMatch = output.match(/Root Token\: (.+)$/m);

View File

@ -1,5 +1,8 @@
import { test } from 'qunit';
import { test, skip } from 'qunit';
import moduleForAcceptance from 'vault/tests/helpers/module-for-acceptance';
import secretList from 'vault/tests/pages/secrets/backend/list';
import secretEdit from 'vault/tests/pages/secrets/backend/kv/edit-secret';
import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
import Ember from 'ember';
let adapterException;
@ -9,7 +12,11 @@ moduleForAcceptance('Acceptance | leases', {
beforeEach() {
adapterException = Ember.Test.adapter.exception;
Ember.Test.adapter.exception = () => null;
return authLogin();
authLogin();
this.enginePath = `kv-for-lease-${new Date().getTime()}`;
// need a version 1 mount for leased secrets here
return mountSecrets.visit().path(this.enginePath).type('kv').version(1).submit();
},
afterEach() {
Ember.Test.adapter.exception = adapterException;
@ -18,43 +25,31 @@ moduleForAcceptance('Acceptance | leases', {
});
const createSecret = (context, isRenewable) => {
const now = new Date().getTime();
const secretContents = { secret: 'foo' };
context.name = `secret-${new Date().getTime()}`;
secretList.visitRoot({ backend: context.enginePath });
secretList.create();
if (isRenewable) {
secretContents.ttl = '30h';
secretEdit.createSecret(context.name, 'ttl', '30h');
} else {
secretEdit.createSecret(context.name, 'foo', 'bar');
}
context.secret = {
name: isRenewable ? `renew-secret-${now}` : `secret-${now}`,
text: JSON.stringify(secretContents),
};
//create a secret so we have a lease (server is running in -dev-leased-kv mode)
visit('/vault/secrets/secret/list');
click('[data-test-secret-create]');
fillIn('[data-test-secret-path]', context.secret.name);
andThen(() => {
const codeMirror = find('.CodeMirror');
// UI keeps state so once we flip to json, we don't need to again
if (!codeMirror.length) {
click('[data-test-secret-json-toggle]');
}
});
andThen(() => {
find('.CodeMirror').get(0).CodeMirror.setValue(context.secret.text);
});
click('[data-test-secret-save]');
};
const navToDetail = secret => {
const navToDetail = context => {
visit('/vault/access/leases/');
click('[data-test-lease-link="secret/"]');
click(`[data-test-lease-link="secret/${secret.name}/"]`);
// all the
click(`[data-test-lease-link="${context.enginePath}/"]`);
// way down
click(`[data-test-lease-link="${context.enginePath}/data/"]`);
// the tree
click(`[data-test-lease-link="${context.enginePath}/data/${context.name}/"]`);
click(`[data-test-lease-link]:eq(0)`);
};
test('it renders the show page', function(assert) {
createSecret(this);
navToDetail(this.secret);
andThen(() => {
navToDetail(this);
return andThen(() => {
assert.equal(
currentRouteName(),
'vault.cluster.access.leases.show',
@ -68,9 +63,10 @@ test('it renders the show page', function(assert) {
});
});
test('it renders the show page with a picker', function(assert) {
// skip for now until we find an easy way to generate a renewable lease
skip('it renders the show page with a picker', function(assert) {
createSecret(this, true);
navToDetail(this.secret);
navToDetail(this);
andThen(() => {
assert.equal(
currentRouteName(),
@ -83,7 +79,7 @@ test('it renders the show page with a picker', function(assert) {
test('it removes leases upon revocation', function(assert) {
createSecret(this);
navToDetail(this.secret);
navToDetail(this);
click('[data-test-lease-revoke] button');
click('[data-test-confirm-button]');
andThen(() => {
@ -93,10 +89,11 @@ test('it removes leases upon revocation', function(assert) {
'it navigates back to the leases root on revocation'
);
});
click('[data-test-lease-link="secret/"]');
click(`[data-test-lease-link="${this.enginePath}/"]`);
click('[data-test-lease-link="data/"]');
andThen(() => {
assert.equal(
find(`[data-test-lease-link="secret/${this.secret.name}/"]`).length,
find(`[data-test-lease-link="${this.enginePath}/data/${this.name}/"]`).length,
0,
'link to the lease was removed with revocation'
);
@ -105,7 +102,7 @@ test('it removes leases upon revocation', function(assert) {
test('it removes branches when a prefix is revoked', function(assert) {
createSecret(this);
visit('/vault/access/leases/list/secret/');
visit(`/vault/access/leases/list/${this.enginePath}`);
click('[data-test-lease-revoke-prefix] button');
click('[data-test-confirm-button]');
andThen(() => {
@ -115,7 +112,7 @@ test('it removes branches when a prefix is revoked', function(assert) {
'it navigates back to the leases root on revocation'
);
assert.equal(
find('[data-test-lease-link="secret/"]').length,
find(`[data-test-lease-link="${this.enginePath}/"]`).length,
0,
'link to the prefix was removed with revocation'
);

View File

@ -4,14 +4,26 @@ import editPage from 'vault/tests/pages/secrets/backend/kv/edit-secret';
import showPage from 'vault/tests/pages/secrets/backend/kv/show';
import listPage from 'vault/tests/pages/secrets/backend/list';
import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
import Pretender from 'pretender';
moduleForAcceptance('Acceptance | secrets/secret/create', {
beforeEach() {
this.server = new Pretender(function() {
this.post('/v1/**', this.passthrough);
this.put('/v1/**', this.passthrough);
this.get('/v1/**', this.passthrough);
this.delete('/v1/**', this.passthrough);
});
return authLogin();
},
afterEach() {
this.server.shutdown();
},
});
test('it creates a secret and redirects', function(assert) {
const path = `kv-${new Date().getTime()}`;
const path = `kv-path-${new Date().getTime()}`;
listPage.visitRoot({ backend: 'secret' });
andThen(() => {
assert.equal(currentRouteName(), 'vault.cluster.secrets.backend.list-root', 'navigates to the list page');
@ -20,7 +32,30 @@ test('it creates a secret and redirects', function(assert) {
listPage.create();
editPage.createSecret(path, 'foo', 'bar');
andThen(() => {
let capabilitiesReq = this.server.passthroughRequests.findBy('url', '/v1/sys/capabilities-self');
assert.equal(
JSON.parse(capabilitiesReq.requestBody).path,
`secret/data/${path}`,
'calls capabilites with the correct path'
);
assert.equal(currentRouteName(), 'vault.cluster.secrets.backend.show', 'redirects to the show page');
assert.ok(showPage.editIsPresent, 'shows the edit button');
});
});
test('version 1 performs the correct capabilities lookup', function(assert) {
let enginePath = `kv-${new Date().getTime()}`;
let secretPath = 'foo/bar';
mountSecrets.visit().path(enginePath).type('kv').version(1).submit();
listPage.create();
editPage.createSecret(secretPath, 'foo', 'bar');
andThen(() => {
let capabilitiesReq = this.server.passthroughRequests.findBy('url', '/v1/sys/capabilities-self');
assert.equal(
JSON.parse(capabilitiesReq.requestBody).path,
`${enginePath}/${secretPath}`,
'calls capabilites with the correct path'
);
});
});

View File

@ -6,6 +6,7 @@ export default create({
path: fillable('[data-test-secret-backend-path]'),
submit: clickable('[data-test-secret-backend-submit]'),
toggleOptions: clickable('[data-test-secret-backend-options]'),
version: fillable('[data-test-secret-backend-version]'),
maxTTLVal: fillable('[data-test-ttl-value]', { scope: '[data-test-secret-backend-max-ttl]' }),
maxTTLUnit: fillable('[data-test-ttl-unit]', { scope: '[data-test-secret-backend-max-ttl]' }),
defaultTTLVal: fillable('[data-test-ttl-value]', { scope: '[data-test-secret-backend-default-ttl]' }),

View File

@ -802,6 +802,10 @@ func (c *Core) checkToken(ctx context.Context, req *logical.Request, unauth bool
}
}
if entity != nil && entity.Disabled {
return nil, te, logical.ErrEntityDisabled
}
// Check if this is a root protected path
rootPath := c.router.RootPath(req.Path)
@ -1319,20 +1323,21 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
return retErr
}
// Validate the token is a root token
acl, te, entity, err := c.fetchACLTokenEntryAndEntity(req.ClientToken)
if err != nil {
// Since there is no token store in standby nodes, sealing cannot
// be done. Ideally, the request has to be forwarded to leader node
// for validation and the operation should be performed. But for now,
// just returning with an error and recommending a vault restart, which
// essentially does the same thing.
// Since there is no token store in standby nodes, sealing cannot be done.
// Ideally, the request has to be forwarded to leader node for validation
// and the operation should be performed. But for now, just returning with
// an error and recommending a vault restart, which essentially does the
// same thing.
if c.standby {
c.logger.Error("vault cannot seal when in standby mode; please restart instead")
retErr = multierror.Append(retErr, errors.New("vault cannot seal when in standby mode; please restart instead"))
c.stateLock.RUnlock()
return retErr
}
// Validate the token is a root token
acl, te, entity, err := c.fetchACLTokenEntryAndEntity(req.ClientToken)
if err != nil {
retErr = multierror.Append(retErr, err)
c.stateLock.RUnlock()
return retErr
@ -1341,10 +1346,12 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
// Audit-log the request before going any further
auth := &logical.Auth{
ClientToken: req.ClientToken,
Policies: te.Policies,
Metadata: te.Meta,
DisplayName: te.DisplayName,
EntityID: te.EntityID,
}
if te != nil {
auth.Policies = te.Policies
auth.Metadata = te.Meta
auth.DisplayName = te.DisplayName
auth.EntityID = te.EntityID
}
logInput := &audit.LogInput{
@ -1358,6 +1365,12 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
return retErr
}
if entity != nil && entity.Disabled {
retErr = multierror.Append(retErr, logical.ErrEntityDisabled)
c.stateLock.RUnlock()
return retErr
}
// Attempt to use the token (decrement num_uses)
// On error bail out; if the token has been revoked, bail out too
if te != nil {
@ -1450,10 +1463,12 @@ func (c *Core) StepDown(req *logical.Request) (retErr error) {
// Audit-log the request before going any further
auth := &logical.Auth{
ClientToken: req.ClientToken,
Policies: te.Policies,
Metadata: te.Meta,
DisplayName: te.DisplayName,
EntityID: te.EntityID,
}
if te != nil {
auth.Policies = te.Policies
auth.Metadata = te.Meta
auth.DisplayName = te.DisplayName
auth.EntityID = te.EntityID
}
logInput := &audit.LogInput{
@ -1466,6 +1481,12 @@ func (c *Core) StepDown(req *logical.Request) (retErr error) {
return retErr
}
if entity != nil && entity.Disabled {
retErr = multierror.Append(retErr, logical.ErrEntityDisabled)
c.stateLock.RUnlock()
return retErr
}
// Attempt to use the token (decrement num_uses)
if te != nil {
te, err = c.tokenStore.UseToken(ctx, te)

View File

@ -434,7 +434,7 @@ var aliasHelp = map[string][2]string{
"",
},
"alias-id-list": {
"List all the entity IDs.",
"List all the alias IDs.",
"",
},
}

View File

@ -45,6 +45,10 @@ vault <command> <path> metadata=key1=value1 metadata=key2=value2
Type: framework.TypeCommaStringSlice,
Description: "Policies to be tied to the entity.",
},
"disabled": {
Type: framework.TypeBool,
Description: "If set true, tokens tied to this identity will not be able to be used (but will not be revoked).",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: i.pathEntityRegister(),
@ -76,6 +80,10 @@ vault <command> <path> metadata=key1=value1 metadata=key2=value2
Type: framework.TypeCommaStringSlice,
Description: "Policies to be tied to the entity.",
},
"disabled": {
Type: framework.TypeBool,
Description: "If set true, tokens tied to this identity will not be able to be used (but will not be revoked).",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: i.pathEntityIDUpdate(),
@ -354,6 +362,11 @@ func (i *IdentityStore) handleEntityUpdateCommon(req *logical.Request, d *framew
entity.Policies = entityPoliciesRaw.([]string)
}
disabledRaw, ok := d.GetOk("disabled")
if ok {
entity.Disabled = disabledRaw.(bool)
}
// Get the name
entityName := d.Get("name").(string)
if entityName != "" {
@ -434,6 +447,7 @@ func (i *IdentityStore) handleEntityReadCommon(entity *identity.Entity) (*logica
respData["metadata"] = entity.Metadata
respData["merged_entity_ids"] = entity.MergedEntityIDs
respData["policies"] = entity.Policies
respData["disabled"] = entity.Disabled
// Convert protobuf timestamp into RFC3339 format
respData["creation_time"] = ptypes.TimestampString(entity.CreationTime)

View File

@ -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")
}
}

View File

@ -68,7 +68,7 @@ func groupAliasPaths(i *IdentityStore) []*framework.Path {
},
HelpSynopsis: strings.TrimSpace(groupAliasHelp["group-alias-by-id"][0]),
HelpDescription: strings.TrimSpace(groupHelp["group-alias-by-id"][1]),
HelpDescription: strings.TrimSpace(groupAliasHelp["group-alias-by-id"][1]),
},
{
Pattern: "group-alias/id/?$",
@ -76,8 +76,8 @@ func groupAliasPaths(i *IdentityStore) []*framework.Path {
logical.ListOperation: i.pathGroupAliasIDList(),
},
HelpSynopsis: strings.TrimSpace(entityHelp["group-alias-id-list"][0]),
HelpDescription: strings.TrimSpace(entityHelp["group-alias-id-list"][1]),
HelpSynopsis: strings.TrimSpace(groupAliasHelp["group-alias-id-list"][0]),
HelpDescription: strings.TrimSpace(groupAliasHelp["group-alias-id-list"][1]),
},
}
}
@ -285,7 +285,7 @@ var groupAliasHelp = map[string][2]string{
"",
},
"group-alias-id-list": {
"List all the entity IDs.",
"List all the group alias IDs.",
"",
},
}

View File

@ -115,8 +115,8 @@ vault <command> <path> metadata=key1=value1 metadata=key2=value2
logical.ListOperation: i.pathGroupIDList(),
},
HelpSynopsis: strings.TrimSpace(entityHelp["group-id-list"][0]),
HelpDescription: strings.TrimSpace(entityHelp["group-id-list"][1]),
HelpSynopsis: strings.TrimSpace(groupHelp["group-id-list"][0]),
HelpDescription: strings.TrimSpace(groupHelp["group-id-list"][1]),
},
}
}

View File

@ -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)
}
}

View File

@ -2309,6 +2309,11 @@ func (i *IdentityStore) refreshExternalGroupMembershipsByEntityID(entityID strin
return err
}
mountAccessor := ""
if len(groupAliases) != 0 {
mountAccessor = groupAliases[0].MountAccessor
}
var newGroups []*identity.Group
for _, alias := range groupAliases {
aliasByFactors, err := i.MemDBAliasByFactors(alias.MountAccessor, alias.Name, true, true)
@ -2352,6 +2357,12 @@ func (i *IdentityStore) refreshExternalGroupMembershipsByEntityID(entityID strin
continue
}
// If the external group is from a different mount, don't remove the
// entity ID from it.
if mountAccessor != "" && group.Alias.MountAccessor != mountAccessor {
continue
}
i.logger.Debug("removing member entity ID from external group", "member_entity_id", entityID, "group_id", group.ID)
group.MemberEntityIDs = strutil.StrListDelete(group.MemberEntityIDs, entityID)

View File

@ -684,6 +684,9 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
logical.UpdateOperation: b.handlePolicySet,
logical.DeleteOperation: b.handlePolicyDelete,
},
HelpSynopsis: strings.TrimSpace(sysHelp["policy"][0]),
HelpDescription: strings.TrimSpace(sysHelp["policy"][1]),
},
&framework.Path{
@ -1096,6 +1099,8 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
logical.DeleteOperation: b.handleRawDelete,
logical.ListOperation: b.handleRawList,
},
HelpSynopsis: strings.TrimSpace(sysHelp["raw"][0]),
HelpDescription: strings.TrimSpace(sysHelp["raw"][1]),
})
}
@ -4039,4 +4044,8 @@ This path responds to the following HTTP methods.
"passthrough_request_headers": {
"A list of headers to whitelist and pass from the request to the backend.",
},
"raw": {
"Write, Read, and Delete data directly in the Storage backend.",
"",
},
}

View File

@ -204,7 +204,7 @@ func (c *Core) handleRequest(ctx context.Context, req *logical.Request) (retResp
// return invalid request so that the status codes can be correct
errType := logical.ErrInvalidRequest
switch ctErr {
case ErrInternalError, logical.ErrPermissionDenied:
case ErrInternalError, logical.ErrPermissionDenied, logical.ErrEntityDisabled:
errType = ctErr
}
@ -519,6 +519,10 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re
return nil, nil, fmt.Errorf("failed to create an entity for the authenticated alias")
}
if entity.Disabled {
return nil, nil, logical.ErrEntityDisabled
}
auth.EntityID = entity.ID
if auth.GroupAliases != nil {
err = c.identityStore.refreshExternalGroupMembershipsByEntityID(auth.EntityID, auth.GroupAliases)

View File

@ -18,6 +18,7 @@ import (
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/helper/locksutil"
"github.com/hashicorp/vault/helper/parseutil"
@ -104,6 +105,8 @@ type TokenStore struct {
salt *salt.Salt
tidyLock int64
identityPoliciesDeriverFunc func(string) (*identity.Entity, []string, error)
}
// NewTokenStore is used to construct a token store that is
@ -119,6 +122,7 @@ func NewTokenStore(ctx context.Context, logger log.Logger, c *Core, config *logi
logger: logger,
tokenLocks: locksutil.CreateLocks(),
saltLock: sync.RWMutex{},
identityPoliciesDeriverFunc: c.fetchEntityAndDerivedPolicies,
}
if c.policyStore != nil {
@ -2204,6 +2208,16 @@ func (ts *TokenStore) handleLookup(ctx context.Context, req *logical.Request, da
resp.Data["issue_time"] = leaseTimes.IssueTime
}
if out.EntityID != "" {
_, identityPolicies, err := ts.identityPoliciesDeriverFunc(out.EntityID)
if err != nil {
return nil, err
}
if len(identityPolicies) != 0 {
resp.Data["identity_policies"] = identityPolicies
}
}
if urltoken {
resp.AddWarning(`Using a token in the path is unsafe as the token can be logged in many places. Please use POST or PUT with the token passed in via the "token" parameter.`)
}

View File

@ -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)
}
}

View File

@ -216,10 +216,17 @@ type ServiceConfig struct {
ServicePath string
}
func (r *iamResourceImpl) SetIamPolicyRequest(p *Policy) (*http.Request, error) {
data := struct {
func (r *iamResourceImpl) SetIamPolicyRequest(p *Policy) (req *http.Request, err error) {
var data interface{}
switch strings.ToLower(r.config.Service.Name) {
// GCS uses a different payload than every other API.
case "storage":
data = p
default:
data = struct {
Policy *Policy `json:"policy,omitempty"`
}{Policy: p}
}
buf, err := googleapi.WithoutDataWrapper.JSONReader(data)
if err != nil {
@ -240,6 +247,12 @@ func (r *iamResourceImpl) constructRequest(httpMtd *HttpMethodCfg, data io.Reade
return nil, err
}
if req.Header == nil {
req.Header = make(http.Header)
}
if data != nil {
req.Header.Set("Content-Type", "application/json")
}
replacementMap := make(map[string]string)
for cId, replaceK := range httpMtd.ReplacementKeys {
rId, ok := r.relativeId.IdTuples[cId]

View File

@ -157,9 +157,9 @@ func (b *backend) serviceAccountPolicyRollback(ctx context.Context, req *logical
return err
}
// Take our any bindings still being used by this role set from roles being removed.
// Take out any bindings still being used by this role set from roles being removed.
rolesToRemove := util.ToSet(entry.Roles)
if rs.AccountId.ResourceName() == entry.AccountId.ResourceName() {
if rs != nil && rs.AccountId.ResourceName() == entry.AccountId.ResourceName() {
currRoles, ok := rs.Bindings[entry.Resource]
if ok {
rolesToRemove = rolesToRemove.Sub(currRoles)

16
vendor/vendor.json vendored
View File

@ -1315,22 +1315,22 @@
"revisionTime": "2018-04-03T19:54:48Z"
},
{
"checksumSHA1": "0wzWab/JiPNjFvIx3yg5ztABJ7M=",
"checksumSHA1": "RPIFaxCjd6lchN99dcfhJ0vqqTI=",
"path": "github.com/hashicorp/vault-plugin-secrets-gcp/plugin",
"revision": "963249c4107fa592ba935ccdddd07155214a83bd",
"revisionTime": "2018-04-03T22:22:59Z"
"revision": "b5ca08100f178f8c3eb138c4f5c4a3d039ed173e",
"revisionTime": "2018-04-17T17:32:19Z"
},
{
"checksumSHA1": "sV9w7yCitfGr2/4RjdKkdZjYAmA=",
"checksumSHA1": "1xWWeopXV4tbHTrVM1eFuH8TCuo=",
"path": "github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil",
"revision": "963249c4107fa592ba935ccdddd07155214a83bd",
"revisionTime": "2018-04-03T22:22:59Z"
"revision": "b5ca08100f178f8c3eb138c4f5c4a3d039ed173e",
"revisionTime": "2018-04-17T17:32:19Z"
},
{
"checksumSHA1": "81kYL49zTBoj1NYczxB2Xbr2d6Y=",
"path": "github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util",
"revision": "963249c4107fa592ba935ccdddd07155214a83bd",
"revisionTime": "2018-04-03T22:22:59Z"
"revision": "b5ca08100f178f8c3eb138c4f5c4a3d039ed173e",
"revisionTime": "2018-04-17T17:32:19Z"
},
{
"checksumSHA1": "m3cQgQrCSuWHiPA339FaZU6LuHU=",

View File

@ -662,8 +662,7 @@ list in order to satisfy that constraint.
- `period` `(string: "")` - If set, indicates that the token generated using
this role should never expire. The token should be renewed within the duration
specified by this value. At each renewal, the token's TTL will be set to the
value of this parameter. The maximum allowed lifetime of tokens issued using
this role.
value of this parameter.
- `policies` `(array: [])` - Policies to be set on tokens issued using this
role.
- `allow_instance_migration` `(bool: false)` - If set, allows migration of the

View File

@ -180,18 +180,30 @@ $ curl \
```json
{
"data": {
"id": "ClientToken",
"policies": [
"web",
"stage"
"accessor": "8609694a-cdbc-db9b-d345-e782dbb562ed",
"creation_time": 1523979354,
"creation_ttl": 2764800,
"display_name": "ldap2-tesla",
"entity_id": "7d2e3179-f69b-450c-7179-ac8ee8bd8ca9",
"expire_time": "2018-05-19T11:35:54.466476215-04:00",
"explicit_max_ttl": 0,
"id": "cf64a70f-3a12-3f6c-791d-6cef6d390eed",
"identity_policies": [
"dev-group-policy"
],
"path": "auth/github/login",
"issue_time": "2018-04-17T11:35:54.466476078-04:00",
"meta": {
"user": "armon",
"organization": "hashicorp"
"username": "tesla"
},
"display_name": "github-armon",
"num_uses": 0
"num_uses": 0,
"orphan": true,
"path": "auth/ldap2/login/tesla",
"policies": [
"default",
"testgroup2-policy"
],
"renewable": true,
"ttl": 2764790
}
}
```
@ -217,18 +229,30 @@ $ curl \
```json
{
"data": {
"id": "ClientToken",
"policies": [
"web",
"stage"
"accessor": "8609694a-cdbc-db9b-d345-e782dbb562ed",
"creation_time": 1523979354,
"creation_ttl": 2764800,
"display_name": "ldap2-tesla",
"entity_id": "7d2e3179-f69b-450c-7179-ac8ee8bd8ca9",
"expire_time": "2018-05-19T11:35:54.466476215-04:00",
"explicit_max_ttl": 0,
"id": "cf64a70f-3a12-3f6c-791d-6cef6d390eed",
"identity_policies": [
"dev-group-policy"
],
"path": "auth/github/login",
"issue_time": "2018-04-17T11:35:54.466476078-04:00",
"meta": {
"user": "armon",
"organization": "hashicorp"
"username": "tesla"
},
"display_name": "github-armon",
"num_uses": 0
"num_uses": 0,
"orphan": true,
"path": "auth/ldap2/login/tesla",
"policies": [
"default",
"testgroup2-policy"
],
"renewable": true,
"ttl": 2764790
}
}
```
@ -249,7 +273,7 @@ Returns information about the client token from the accessor.
```json
{
"accessor": "2c84f488-2133-4ced-87b0-570f93a76830"
"accessor": "8609694a-cdbc-db9b-d345-e782dbb562ed"
}
```
@ -267,25 +291,32 @@ $ curl \
```json
{
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"creation_time": 1457533232,
"accessor": "8609694a-cdbc-db9b-d345-e782dbb562ed",
"creation_time": 1523979354,
"creation_ttl": 2764800,
"display_name": "token",
"meta": null,
"display_name": "ldap2-tesla",
"entity_id": "7d2e3179-f69b-450c-7179-ac8ee8bd8ca9",
"expire_time": "2018-05-19T11:35:54.466476215-04:00",
"explicit_max_ttl": 0,
"id": "",
"identity_policies": [
"dev-group-policy"
],
"issue_time": "2018-04-17T11:35:54.466476078-04:00",
"meta": {
"username": "tesla"
},
"num_uses": 0,
"orphan": false,
"path": "auth/token/create",
"orphan": true,
"path": "auth/ldap2/login/tesla",
"policies": [
"default",
"web"
"testgroup2-policy"
],
"ttl": 2591976
},
"warnings": null,
"auth": null
"renewable": true,
"ttl": 2763902
}
}
```

View File

@ -26,6 +26,9 @@ This endpoint creates or updates an Entity.
- `policies` `(list of strings: [])` Policies to be tied to the entity.
- `disabled` `(bool: false)` Whether the entity is disabled. Disabled
entities' associated tokens cannot be used, but are not revoked.
### Sample Payload
```json
@ -86,6 +89,7 @@ $ curl \
"data": {
"bucket_key_hash": "177553e4c58987f4cc5d7e530136c642",
"creation_time": "2017-07-25T20:29:22.614756844Z",
"disabled": false,
"id": "8d6a45e5-572f-8f13-d226-cd0d1ec57297",
"last_update_time": "2017-07-25T20:29:22.614756844Z",
"metadata": {
@ -120,6 +124,8 @@ This endpoint is used to update an existing entity.
- `policies` `(list of strings: [])` Policies to be tied to the entity.
- `disabled` `(bool: false)` Whether the entity is disabled. Disabled
entities' associated tokens cannot be used, but are not revoked.
### Sample Payload

View File

@ -222,7 +222,7 @@ can be achieved without `sudo` via `sys/mounts/auth/[auth-path]/tune`._
object.
- `listing_visibility` `(string: "")` - Speficies whether to show this mount
in the UI-specific listing endpoint.
in the UI-specific listing endpoint. Valid values are `"unauth"` or `""`.
- `passthrough_request_headers` `(array: [])` - Comma-separated list of headers
to whitelist and pass from the request to the backend.

View File

@ -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"
}
}
}
```

View File

@ -223,7 +223,7 @@ This endpoint tunes configuration parameters for a given mount point.
object.
- `listing_visibility` `(string: "")` - Speficies whether to show this mount
in the UI-specific listing endpoint.
in the UI-specific listing endpoint. Valid values are `"unauth"` or `""`.
- `passthrough_request_headers` `(array: [])` - Comma-separated list of headers
to whitelist and pass from the request to the backend.

View File

@ -92,7 +92,7 @@ A single CLI command can be used to create a new OTP and invoke SSH with the
correct parameters to connect to the host.
```text
$ vault ssh -role otp_key_role username@x.x.x.x
$ vault ssh -role otp_key_role -mode otp username@x.x.x.x
OTP for the session is `b4d47e1b-4879-5f4e-ce5c-7988d7986f37`
[Note: Install `sshpass` to automate typing in OTP]
Password: <Enter OTP>
@ -101,7 +101,7 @@ Password: <Enter OTP>
The OTP will be entered automatically using `sshpass` if it is installed.
```text
$ vault ssh -role otp_key_role -strict-host-key-checking=no username@x.x.x.x
$ vault ssh -role otp_key_role -mode otp -strict-host-key-checking=no username@x.x.x.x
username@<IP of remote host>:~$
```

View File

@ -46,7 +46,7 @@ $ vault kv put secret/hello foo=world excited=yes
Success! Data written to: secret/hello
```
`vault write` is a very powerful command. In addition to writing data
`vault kv put` is a very powerful command. In addition to writing data
directly from the command-line, it can read values and key pairs from
`STDIN` as well as files. For more information, see the
[command documentation](/docs/commands/index.html).

View File

@ -219,6 +219,9 @@
<li<%= sidebar_current("docs-http-system-init") %>>
<a href="/api/system/init.html"><tt>/sys/init</tt></a>
</li>
<li<%= sidebar_current("docs-http-system-internal-ui-mounts") %>>
<a href="/api/system/internal-ui-mounts.html"><tt>/sys/internal/ui/mounts</tt></a>
</li>
<li<%= sidebar_current("docs-http-system-key-status") %>>
<a href="/api/system/key-status.html"><tt>/sys/key-status</tt></a>
</li>