Add fields to the /acl/auth-methods endpoint. (#9741)
* A GET of the /acl/auth-method/:name endpoint returns the fields MaxTokenTTL and TokenLocality, while a LIST (/acl/auth-methods) does not. The list command returns a filtered subset of the full set. This is somewhat deliberate, so that secrets aren't shown, but the TTL and Locality fields aren't (IMO) security critical, and it is useful for the front end to be able to show them. For consistency these changes mirror the 'omit empty' and string representation choices made for the GET call. This includes changes to the gRPC and API code in the client. The new output looks similar to this curl 'http://localhost:8500/v1/acl/auth-methods' | jq '.' { "MaxTokenTTL": "8m20s", "Name": "minikube-ttl-local2", "Type": "kubernetes", "Description": "minikube auth method", "TokenLocality": "local", "CreateIndex": 530, "ModifyIndex": 530, "Namespace": "default" } ] Signed-off-by: Mark Anderson <manderson@hashicorp.com> * Add changelog Signed-off-by: Mark Anderson <manderson@hashicorp.com>
This commit is contained in:
parent
15913a5e30
commit
1cf6a435dc
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
acl: extend the auth-methods list endpoint to include MaxTokenTTL and TokenLocality fields.
|
||||||
|
```
|
|
@ -1201,6 +1201,8 @@ func TestACL_LoginProcedure_HTTP(t *testing.T) {
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
"SessionID": testSessionID,
|
"SessionID": testSessionID,
|
||||||
},
|
},
|
||||||
|
TokenLocality: "global",
|
||||||
|
MaxTokenTTL: 500_000_000_000,
|
||||||
}
|
}
|
||||||
|
|
||||||
req, _ := http.NewRequest("PUT", "/v1/acl/auth-method?token=root", jsonBody(methodInput))
|
req, _ := http.NewRequest("PUT", "/v1/acl/auth-method?token=root", jsonBody(methodInput))
|
||||||
|
@ -1284,6 +1286,7 @@ func TestACL_LoginProcedure_HTTP(t *testing.T) {
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
raw, err := a.srv.ACLAuthMethodList(resp, req)
|
raw, err := a.srv.ACLAuthMethodList(resp, req)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
methods, ok := raw.(structs.ACLAuthMethodListStubs)
|
methods, ok := raw.(structs.ACLAuthMethodListStubs)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
|
@ -1297,6 +1300,8 @@ func TestACL_LoginProcedure_HTTP(t *testing.T) {
|
||||||
require.Equal(t, expected.Name, actual.Name)
|
require.Equal(t, expected.Name, actual.Name)
|
||||||
require.Equal(t, expected.Type, actual.Type)
|
require.Equal(t, expected.Type, actual.Type)
|
||||||
require.Equal(t, expected.Description, actual.Description)
|
require.Equal(t, expected.Description, actual.Description)
|
||||||
|
require.Equal(t, expected.MaxTokenTTL, actual.MaxTokenTTL)
|
||||||
|
require.Equal(t, expected.TokenLocality, actual.TokenLocality)
|
||||||
require.Equal(t, expected.CreateIndex, actual.CreateIndex)
|
require.Equal(t, expected.CreateIndex, actual.CreateIndex)
|
||||||
require.Equal(t, expected.ModifyIndex, actual.ModifyIndex)
|
require.Equal(t, expected.ModifyIndex, actual.ModifyIndex)
|
||||||
found = true
|
found = true
|
||||||
|
|
|
@ -1090,13 +1090,16 @@ func (rules ACLBindingRules) Sort() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: this is a subset of ACLAuthMethod's fields
|
||||||
type ACLAuthMethodListStub struct {
|
type ACLAuthMethodListStub struct {
|
||||||
Name string
|
Name string
|
||||||
Type string
|
Type string
|
||||||
DisplayName string `json:",omitempty"`
|
DisplayName string `json:",omitempty"`
|
||||||
Description string `json:",omitempty"`
|
Description string `json:",omitempty"`
|
||||||
CreateIndex uint64
|
MaxTokenTTL time.Duration `json:",omitempty"`
|
||||||
ModifyIndex uint64
|
TokenLocality string `json:",omitempty"`
|
||||||
|
CreateIndex uint64
|
||||||
|
ModifyIndex uint64
|
||||||
EnterpriseMeta
|
EnterpriseMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1106,12 +1109,34 @@ func (p *ACLAuthMethod) Stub() *ACLAuthMethodListStub {
|
||||||
Type: p.Type,
|
Type: p.Type,
|
||||||
DisplayName: p.DisplayName,
|
DisplayName: p.DisplayName,
|
||||||
Description: p.Description,
|
Description: p.Description,
|
||||||
|
MaxTokenTTL: p.MaxTokenTTL,
|
||||||
|
TokenLocality: p.TokenLocality,
|
||||||
CreateIndex: p.CreateIndex,
|
CreateIndex: p.CreateIndex,
|
||||||
ModifyIndex: p.ModifyIndex,
|
ModifyIndex: p.ModifyIndex,
|
||||||
EnterpriseMeta: p.EnterpriseMeta,
|
EnterpriseMeta: p.EnterpriseMeta,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is nearly identical to the ACLAuthMethod MarshalJSON
|
||||||
|
// Unmarshaling is not implemented because the API is read only
|
||||||
|
func (m *ACLAuthMethodListStub) MarshalJSON() ([]byte, error) {
|
||||||
|
type Alias ACLAuthMethodListStub
|
||||||
|
exported := &struct {
|
||||||
|
MaxTokenTTL string `json:",omitempty"`
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
MaxTokenTTL: m.MaxTokenTTL.String(),
|
||||||
|
Alias: (*Alias)(m),
|
||||||
|
}
|
||||||
|
if m.MaxTokenTTL == 0 {
|
||||||
|
exported.MaxTokenTTL = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(exported)
|
||||||
|
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
type ACLAuthMethods []*ACLAuthMethod
|
type ACLAuthMethods []*ACLAuthMethod
|
||||||
type ACLAuthMethodListStubs []*ACLAuthMethodListStub
|
type ACLAuthMethodListStubs []*ACLAuthMethodListStub
|
||||||
|
|
||||||
|
|
53
api/acl.go
53
api/acl.go
|
@ -270,16 +270,61 @@ type ACLAuthMethodNamespaceRule struct {
|
||||||
type ACLAuthMethodListEntry struct {
|
type ACLAuthMethodListEntry struct {
|
||||||
Name string
|
Name string
|
||||||
Type string
|
Type string
|
||||||
DisplayName string `json:",omitempty"`
|
DisplayName string `json:",omitempty"`
|
||||||
Description string `json:",omitempty"`
|
Description string `json:",omitempty"`
|
||||||
CreateIndex uint64
|
MaxTokenTTL time.Duration `json:",omitempty"`
|
||||||
ModifyIndex uint64
|
|
||||||
|
// TokenLocality defines the kind of token that this auth method produces.
|
||||||
|
// This can be either 'local' or 'global'. If empty 'local' is assumed.
|
||||||
|
TokenLocality string `json:",omitempty"`
|
||||||
|
CreateIndex uint64
|
||||||
|
ModifyIndex uint64
|
||||||
|
|
||||||
// Namespace is the namespace the ACLAuthMethodListEntry is associated with.
|
// Namespace is the namespace the ACLAuthMethodListEntry is associated with.
|
||||||
// Namespacing is a Consul Enterprise feature.
|
// Namespacing is a Consul Enterprise feature.
|
||||||
Namespace string `json:",omitempty"`
|
Namespace string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is nearly identical to the ACLAuthMethod MarshalJSON
|
||||||
|
func (m *ACLAuthMethodListEntry) MarshalJSON() ([]byte, error) {
|
||||||
|
type Alias ACLAuthMethodListEntry
|
||||||
|
exported := &struct {
|
||||||
|
MaxTokenTTL string `json:",omitempty"`
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
MaxTokenTTL: m.MaxTokenTTL.String(),
|
||||||
|
Alias: (*Alias)(m),
|
||||||
|
}
|
||||||
|
if m.MaxTokenTTL == 0 {
|
||||||
|
exported.MaxTokenTTL = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(exported)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is nearly identical to the ACLAuthMethod UnmarshalJSON
|
||||||
|
func (m *ACLAuthMethodListEntry) UnmarshalJSON(data []byte) error {
|
||||||
|
type Alias ACLAuthMethodListEntry
|
||||||
|
aux := &struct {
|
||||||
|
MaxTokenTTL string
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(m),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if aux.MaxTokenTTL != "" {
|
||||||
|
if m.MaxTokenTTL, err = time.ParseDuration(aux.MaxTokenTTL); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ParseKubernetesAuthMethodConfig takes a raw config map and returns a parsed
|
// ParseKubernetesAuthMethodConfig takes a raw config map and returns a parsed
|
||||||
// KubernetesAuthMethodConfig.
|
// KubernetesAuthMethodConfig.
|
||||||
func ParseKubernetesAuthMethodConfig(raw map[string]interface{}) (*KubernetesAuthMethodConfig, error) {
|
func ParseKubernetesAuthMethodConfig(raw map[string]interface{}) (*KubernetesAuthMethodConfig, error) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package api
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/sdk/testutil/retry"
|
"github.com/hashicorp/consul/sdk/testutil/retry"
|
||||||
|
|
||||||
|
@ -657,6 +658,101 @@ func TestAPI_ACLToken_Clone(t *testing.T) {
|
||||||
require.Equal(t, cloned, read)
|
require.Equal(t, cloned, read)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
func TestAPI_AuthMethod_List(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
c, s := makeACLClient(t)
|
||||||
|
defer s.Stop()
|
||||||
|
|
||||||
|
acl := c.ACL()
|
||||||
|
s.WaitForSerfCheck(t)
|
||||||
|
|
||||||
|
method1 := ACLAuthMethod{
|
||||||
|
Name: "test_1",
|
||||||
|
Type: "kubernetes",
|
||||||
|
Description: "test 1",
|
||||||
|
MaxTokenTTL: 260 * time.Second,
|
||||||
|
TokenLocality: "global",
|
||||||
|
Config: AuthMethodCreateKubernetesConfigHelper(),
|
||||||
|
}
|
||||||
|
|
||||||
|
created1, wm, err := acl.AuthMethodCreate(&method1, nil)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, created1)
|
||||||
|
require.NotEqual(t, "", created1.Name)
|
||||||
|
require.NotEqual(t, 0, wm.RequestTime)
|
||||||
|
|
||||||
|
method2 := ACLAuthMethod{
|
||||||
|
Name: "test_2",
|
||||||
|
Type: "kubernetes",
|
||||||
|
Description: "test 2",
|
||||||
|
MaxTokenTTL: 0,
|
||||||
|
TokenLocality: "local",
|
||||||
|
Config: AuthMethodCreateKubernetesConfigHelper(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = acl.AuthMethodCreate(&method2, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
entries, _, err := acl.AuthMethodList(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, entries)
|
||||||
|
require.Equal(t, 2, len(entries))
|
||||||
|
|
||||||
|
{
|
||||||
|
entry := entries[0]
|
||||||
|
require.Equal(t, "test_1", entry.Name)
|
||||||
|
require.Equal(t, 260*time.Second, entry.MaxTokenTTL)
|
||||||
|
require.Equal(t, "global", entry.TokenLocality)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
entry := entries[1]
|
||||||
|
require.Equal(t, "test_2", entry.Name)
|
||||||
|
require.Equal(t, time.Duration(0), entry.MaxTokenTTL)
|
||||||
|
require.Equal(t, "local", entry.TokenLocality)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AuthMethodCreateKubernetesConfigHelper() (result map[string]interface{}) {
|
||||||
|
var pemData = `
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIE1DCCArwCCQC2kx7TchbxAzANBgkqhkiG9w0BAQsFADAsMQswCQYDVQQGEwJV
|
||||||
|
UzELMAkGA1UECAwCV0ExEDAOBgNVBAcMB1NlYXR0bGUwHhcNMjEwMTI3MDIzNDA1
|
||||||
|
WhcNMjIwMTI3MDIzNDA1WjAsMQswCQYDVQQGEwJVUzELMAkGA1UECAwCV0ExEDAO
|
||||||
|
BgNVBAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCt
|
||||||
|
j3zRFLg2A2DcZFwoc1HvIsGzqcfvxjee/OQjKyIuXbdpbJGIahB2piNYtd49zU/5
|
||||||
|
ofRAuqIQOco3V9LfL52I7NchNBvPQOrXjbpcM3qF2qQvunVlnnaPCIf8S5hsFMaq
|
||||||
|
w2/+jnLjaUdXGJ9bold5E/bms87uRahvhUpY7MhkSDNsAen+YThpwucc9JFRmrz3
|
||||||
|
EXGtTzcpyEn9b0s6ut9mum2UVqghAQyLeW8cNx1zeg6Bi5USjOKF6CQgF7o4kZ9X
|
||||||
|
D0Nk5vB9eePs/q5N9LHkDFKVCmzAYgzcQeGZFEzNcgK7N5y+aB2xXKpH3tydpwRd
|
||||||
|
uS+g05Jvk8M8P34wteUb8tq3jZuY7UYzlINMSrPuZdFhcGjmxPjC5hl1SZy4vF1s
|
||||||
|
GAD9RsleTZ8yeC6Cfo4mba214C9CqYkC2NBw2HO53pzO/tYI844QPhjmVBJ7bb35
|
||||||
|
S052HD7m+AzbfY6w9CDH4D4mzIM4u1yRB6OlXdXTH58BhgxHdEnugLYr13QlVWRW
|
||||||
|
4nZgMFKiTY7cBscpPcVRsne/VR9VwSatp3adj+G8+WUtwQLJC2OcCFYvmHfdSOs0
|
||||||
|
B15LH/tGeJcfKViKC9ifPq5abVZByr66jTQMAdBWet03OBnmLqJs9TI4wci0MkK/
|
||||||
|
HlHYdy734rReD81LY9fCRCRFV4ZtMx2rfj7cqgKLlwIDAQABMA0GCSqGSIb3DQEB
|
||||||
|
CwUAA4ICAQB6ji6wA9ROFx8ZhLPlEnDiielSUN8LR2K8cmAjxxffJo3GxRH/zZYl
|
||||||
|
CM+DzU5VVzW6RGWuTNzcFNsxlaRx20sj5RyXLH90wFYLO2Rrs1XKWmqpfdN0Iiue
|
||||||
|
W7rYdNPV7YPjIVQVoijEt8kwx24jE9mU5ILXe4+WKPWavG+dHA1r8lQdg7wmE/8R
|
||||||
|
E/nSVtusuX0JRVdL96iy2HB37DYj+rJEE0C7fKAk51o0C4F6fOzUsWCaP/23pZNI
|
||||||
|
rA6hCq2CJeT4ObVukCIrnylrckZs8ElcZ7PvJ9bCNvma+dAxbL0uEkv0q0feLeVh
|
||||||
|
OTttNIVTUjYjr3KE6rtE1Rr35R/6HCK+zZDOkKf+TVEQsFuI4DRVEuntzjo9bgZf
|
||||||
|
fAL6G+UXpzW440BJzmzADnSthawMZFdqVrrBzpzb+B2d9VLDEoyCCFzaJyj/Gyff
|
||||||
|
kqxRFTHZJRKC/3iIRXOX64bIr1YmXHFHCBkcq7eyh1oeaTrGZ43HimaveWwcsPv/
|
||||||
|
SxTJANJHqf4BiFtVjN7LZXi3HUIRAsceEbd0TfW5be9SQ0tbDyyGYt/bXtBLGTIh
|
||||||
|
9kerr9eWDHlpHMTyP01+Ua3EacbfgrmvD9sa3s6gC4SnwlvLdubmyLwoorCs77eF
|
||||||
|
15bSOU7NsVZfwLw+M+DyNWPxI1BR/XOP+YoyTgIEChIC9eYnmlWU2Q==
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
|
||||||
|
result = map[string]interface{}{
|
||||||
|
"Host": "https://192.0.2.42:8443",
|
||||||
|
"CACert": pemData,
|
||||||
|
"ServiceAccountJWT": `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImp0aSI6ImQxYTZiYzE5LWZiODItNDI5ZC05NmUxLTg1YTFjYjEyNGQ3MCIsImlhdCI6MTYxMTcxNTQ5NiwiZXhwIjoxNjExNzE5MDk2fQ.rrVS5h1Yw20eI41RsTl2YAqzKKikKNg3qMkDmspTPQs`,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPI_RulesTranslate_FromToken(t *testing.T) {
|
func TestAPI_RulesTranslate_FromToken(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
c, s := makeACLClient(t)
|
c, s := makeACLClient(t)
|
||||||
|
|
Loading…
Reference in New Issue