mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
Merge pull request #5991 from gyuho/manual
v2http: client certificate auth via common name
This commit is contained in:
commit
de638a5e4d
@ -276,6 +276,42 @@ func TestCtlV2Backup(t *testing.T) { // For https://github.com/coreos/etcd/issue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCtlV2AuthWithCommonName(t *testing.T) {
|
||||||
|
defer testutil.AfterTest(t)
|
||||||
|
|
||||||
|
copiedCfg := configClientTLS
|
||||||
|
copiedCfg.clientCertAuthEnabled = true
|
||||||
|
|
||||||
|
epc := setupEtcdctlTest(t, &copiedCfg, false)
|
||||||
|
defer func() {
|
||||||
|
if err := epc.Close(); err != nil {
|
||||||
|
t.Fatalf("error closing etcd processes (%v)", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := etcdctlRoleAdd(epc, "testrole"); err != nil {
|
||||||
|
t.Fatalf("failed to add role (%v)", err)
|
||||||
|
}
|
||||||
|
if err := etcdctlRoleGrant(epc, "testrole", "--rw", "--path=/foo"); err != nil {
|
||||||
|
t.Fatalf("failed to grant role (%v)", err)
|
||||||
|
}
|
||||||
|
if err := etcdctlUserAdd(epc, "root", "123"); err != nil {
|
||||||
|
t.Fatalf("failed to add user (%v)", err)
|
||||||
|
}
|
||||||
|
if err := etcdctlUserAdd(epc, "Autogenerated CA", "123"); err != nil {
|
||||||
|
t.Fatalf("failed to add user (%v)", err)
|
||||||
|
}
|
||||||
|
if err := etcdctlUserGrant(epc, "Autogenerated CA", "testrole"); err != nil {
|
||||||
|
t.Fatalf("failed to grant role (%v)", err)
|
||||||
|
}
|
||||||
|
if err := etcdctlAuthEnable(epc); err != nil {
|
||||||
|
t.Fatalf("failed to enable auth (%v)", err)
|
||||||
|
}
|
||||||
|
if err := etcdctlSet(epc, "foo", "bar"); err != nil {
|
||||||
|
t.Fatalf("failed to write (%v)", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func etcdctlPrefixArgs(clus *etcdProcessCluster) []string {
|
func etcdctlPrefixArgs(clus *etcdProcessCluster) []string {
|
||||||
endpoints := ""
|
endpoints := ""
|
||||||
if proxies := clus.proxies(); len(proxies) != 0 {
|
if proxies := clus.proxies(); len(proxies) != 0 {
|
||||||
@ -348,6 +384,13 @@ func etcdctlRoleAdd(clus *etcdProcessCluster, role string) error {
|
|||||||
return spawnWithExpect(cmdArgs, role)
|
return spawnWithExpect(cmdArgs, role)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func etcdctlRoleGrant(clus *etcdProcessCluster, role string, perms ...string) error {
|
||||||
|
cmdArgs := append(etcdctlPrefixArgs(clus), "role", "grant")
|
||||||
|
cmdArgs = append(cmdArgs, perms...)
|
||||||
|
cmdArgs = append(cmdArgs, role)
|
||||||
|
return spawnWithExpect(cmdArgs, role)
|
||||||
|
}
|
||||||
|
|
||||||
func etcdctlRoleList(clus *etcdProcessCluster, expectedRole string) error {
|
func etcdctlRoleList(clus *etcdProcessCluster, expectedRole string) error {
|
||||||
cmdArgs := append(etcdctlPrefixArgs(clus), "role", "list")
|
cmdArgs := append(etcdctlPrefixArgs(clus), "role", "list")
|
||||||
return spawnWithExpect(cmdArgs, expectedRole)
|
return spawnWithExpect(cmdArgs, expectedRole)
|
||||||
|
@ -150,6 +150,7 @@ type etcdProcessClusterConfig struct {
|
|||||||
snapCount int // default is 10000
|
snapCount int // default is 10000
|
||||||
|
|
||||||
clientTLS clientConnType
|
clientTLS clientConnType
|
||||||
|
clientCertAuthEnabled bool
|
||||||
isPeerTLS bool
|
isPeerTLS bool
|
||||||
isPeerAutoTLS bool
|
isPeerAutoTLS bool
|
||||||
isClientAutoTLS bool
|
isClientAutoTLS bool
|
||||||
@ -325,6 +326,10 @@ func (cfg *etcdProcessClusterConfig) tlsArgs() (args []string) {
|
|||||||
"--ca-file", caPath,
|
"--ca-file", caPath,
|
||||||
}
|
}
|
||||||
args = append(args, tlsClientArgs...)
|
args = append(args, tlsClientArgs...)
|
||||||
|
|
||||||
|
if cfg.clientCertAuthEnabled {
|
||||||
|
args = append(args, "--client-cert-auth")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +116,7 @@ func StartEtcd(inCfg *Config) (e *Etcd, err error) {
|
|||||||
QuotaBackendBytes: cfg.QuotaBackendBytes,
|
QuotaBackendBytes: cfg.QuotaBackendBytes,
|
||||||
StrictReconfigCheck: cfg.StrictReconfigCheck,
|
StrictReconfigCheck: cfg.StrictReconfigCheck,
|
||||||
EnablePprof: cfg.EnablePprof,
|
EnablePprof: cfg.EnablePprof,
|
||||||
|
ClientCertAuthEnabled: cfg.ClientTLSInfo.ClientCertAuth,
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Server, err = etcdserver.NewServer(srvcfg); err != nil {
|
if e.Server, err = etcdserver.NewServer(srvcfg); err != nil {
|
||||||
|
@ -70,6 +70,7 @@ func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http
|
|||||||
cluster: server.Cluster(),
|
cluster: server.Cluster(),
|
||||||
timer: server,
|
timer: server,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
|
clientCertAuthEnabled: server.Cfg.ClientCertAuthEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
sh := &statsHandler{
|
sh := &statsHandler{
|
||||||
@ -82,6 +83,7 @@ func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http
|
|||||||
cluster: server.Cluster(),
|
cluster: server.Cluster(),
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
clock: clockwork.NewRealClock(),
|
clock: clockwork.NewRealClock(),
|
||||||
|
clientCertAuthEnabled: server.Cfg.ClientCertAuthEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
dmh := &deprecatedMachinesHandler{
|
dmh := &deprecatedMachinesHandler{
|
||||||
@ -91,6 +93,7 @@ func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http
|
|||||||
sech := &authHandler{
|
sech := &authHandler{
|
||||||
sec: sec,
|
sec: sec,
|
||||||
cluster: server.Cluster(),
|
cluster: server.Cluster(),
|
||||||
|
clientCertAuthEnabled: server.Cfg.ClientCertAuthEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
@ -137,6 +140,7 @@ type keysHandler struct {
|
|||||||
cluster api.Cluster
|
cluster api.Cluster
|
||||||
timer etcdserver.RaftTimer
|
timer etcdserver.RaftTimer
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
|
clientCertAuthEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -156,7 +160,7 @@ func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// The path must be valid at this point (we've parsed the request successfully).
|
// The path must be valid at this point (we've parsed the request successfully).
|
||||||
if !hasKeyPrefixAccess(h.sec, r, r.URL.Path[len(keysPrefix):], rr.Recursive) {
|
if !hasKeyPrefixAccess(h.sec, r, r.URL.Path[len(keysPrefix):], rr.Recursive, h.clientCertAuthEnabled) {
|
||||||
writeKeyNoAuth(w)
|
writeKeyNoAuth(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -204,13 +208,14 @@ type membersHandler struct {
|
|||||||
cluster api.Cluster
|
cluster api.Cluster
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
clock clockwork.Clock
|
clock clockwork.Clock
|
||||||
|
clientCertAuthEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if !allowMethod(w, r.Method, "GET", "POST", "DELETE", "PUT") {
|
if !allowMethod(w, r.Method, "GET", "POST", "DELETE", "PUT") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !hasWriteRootAccess(h.sec, r) {
|
if !hasWriteRootAccess(h.sec, r, h.clientCertAuthEnabled) {
|
||||||
writeNoAuth(w, r)
|
writeNoAuth(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -28,16 +28,54 @@ import (
|
|||||||
type authHandler struct {
|
type authHandler struct {
|
||||||
sec auth.Store
|
sec auth.Store
|
||||||
cluster api.Cluster
|
cluster api.Cluster
|
||||||
|
clientCertAuthEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasWriteRootAccess(sec auth.Store, r *http.Request) bool {
|
func hasWriteRootAccess(sec auth.Store, r *http.Request, clientCertAuthEnabled bool) bool {
|
||||||
if r.Method == "GET" || r.Method == "HEAD" {
|
if r.Method == "GET" || r.Method == "HEAD" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return hasRootAccess(sec, r)
|
return hasRootAccess(sec, r, clientCertAuthEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasRootAccess(sec auth.Store, r *http.Request) bool {
|
func userFromBasicAuth(sec auth.Store, r *http.Request) *auth.User {
|
||||||
|
username, password, ok := r.BasicAuth()
|
||||||
|
if !ok {
|
||||||
|
plog.Warningf("auth: malformed basic auth encoding")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
user, err := sec.GetUser(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = sec.CheckPassword(user, password)
|
||||||
|
if !ok {
|
||||||
|
plog.Warningf("auth: incorrect password for user: %s", username)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &user
|
||||||
|
}
|
||||||
|
|
||||||
|
func userFromClientCertificate(sec auth.Store, r *http.Request) *auth.User {
|
||||||
|
if r.TLS == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, chains := range r.TLS.VerifiedChains {
|
||||||
|
for _, chain := range chains {
|
||||||
|
plog.Debugf("auth: found common name %s.\n", chain.Subject.CommonName)
|
||||||
|
user, err := sec.GetUser(chain.Subject.CommonName)
|
||||||
|
if err == nil {
|
||||||
|
plog.Debugf("auth: authenticated user %s by cert common name.", user.User)
|
||||||
|
return &user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasRootAccess(sec auth.Store, r *http.Request, clientCertAuthEnabled bool) bool {
|
||||||
if sec == nil {
|
if sec == nil {
|
||||||
// No store means no auth available, eg, tests.
|
// No store means no auth available, eg, tests.
|
||||||
return true
|
return true
|
||||||
@ -45,30 +83,30 @@ func hasRootAccess(sec auth.Store, r *http.Request) bool {
|
|||||||
if !sec.AuthEnabled() {
|
if !sec.AuthEnabled() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
username, password, ok := r.BasicAuth()
|
|
||||||
if !ok {
|
var rootUser *auth.User
|
||||||
|
if r.Header.Get("Authorization") == "" && clientCertAuthEnabled {
|
||||||
|
rootUser = userFromClientCertificate(sec, r)
|
||||||
|
if rootUser == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
rootUser, err := sec.GetUser(username)
|
} else {
|
||||||
if err != nil {
|
rootUser = userFromBasicAuth(sec, r)
|
||||||
|
if rootUser == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ok = sec.CheckPassword(rootUser, password)
|
|
||||||
if !ok {
|
|
||||||
plog.Warningf("auth: wrong password for user %s", username)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, role := range rootUser.Roles {
|
for _, role := range rootUser.Roles {
|
||||||
if role == auth.RootRoleName {
|
if role == auth.RootRoleName {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
plog.Warningf("auth: user %s does not have the %s role for resource %s.", username, auth.RootRoleName, r.URL.Path)
|
plog.Warningf("auth: user %s does not have the %s role for resource %s.", rootUser.User, auth.RootRoleName, r.URL.Path)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive bool) bool {
|
func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive, clientCertAuthEnabled bool) bool {
|
||||||
if sec == nil {
|
if sec == nil {
|
||||||
// No store means no auth available, eg, tests.
|
// No store means no auth available, eg, tests.
|
||||||
return true
|
return true
|
||||||
@ -76,25 +114,21 @@ func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive b
|
|||||||
if !sec.AuthEnabled() {
|
if !sec.AuthEnabled() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if r.Header.Get("Authorization") == "" {
|
|
||||||
|
var user *auth.User
|
||||||
|
if r.Header.Get("Authorization") == "" && clientCertAuthEnabled {
|
||||||
|
user = userFromClientCertificate(sec, r)
|
||||||
|
if user == nil {
|
||||||
plog.Warningf("auth: no authorization provided, checking guest access")
|
plog.Warningf("auth: no authorization provided, checking guest access")
|
||||||
return hasGuestAccess(sec, r, key)
|
return hasGuestAccess(sec, r, key)
|
||||||
}
|
}
|
||||||
username, password, ok := r.BasicAuth()
|
} else {
|
||||||
if !ok {
|
user = userFromBasicAuth(sec, r)
|
||||||
plog.Warningf("auth: malformed basic auth encoding")
|
if user == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
user, err := sec.GetUser(username)
|
|
||||||
if err != nil {
|
|
||||||
plog.Warningf("auth: no such user: %s.", username)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
authAsUser := sec.CheckPassword(user, password)
|
|
||||||
if !authAsUser {
|
|
||||||
plog.Warningf("auth: incorrect password for user: %s.", username)
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writeAccess := r.Method != "GET" && r.Method != "HEAD"
|
writeAccess := r.Method != "GET" && r.Method != "HEAD"
|
||||||
for _, roleName := range user.Roles {
|
for _, roleName := range user.Roles {
|
||||||
role, err := sec.GetRole(roleName)
|
role, err := sec.GetRole(roleName)
|
||||||
@ -109,7 +143,7 @@ func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive b
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
plog.Warningf("auth: invalid access for user %s on key %s.", username, key)
|
plog.Warningf("auth: invalid access for user %s on key %s.", user.User, key)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +179,7 @@ func (sh *authHandler) baseRoles(w http.ResponseWriter, r *http.Request) {
|
|||||||
if !allowMethod(w, r.Method, "GET") {
|
if !allowMethod(w, r.Method, "GET") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !hasRootAccess(sh.sec, r) {
|
if !hasRootAccess(sh.sec, r, sh.clientCertAuthEnabled) {
|
||||||
writeNoAuth(w, r)
|
writeNoAuth(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -209,7 +243,7 @@ func (sh *authHandler) forRole(w http.ResponseWriter, r *http.Request, role stri
|
|||||||
if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") {
|
if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !hasRootAccess(sh.sec, r) {
|
if !hasRootAccess(sh.sec, r, sh.clientCertAuthEnabled) {
|
||||||
writeNoAuth(w, r)
|
writeNoAuth(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -293,7 +327,7 @@ func (sh *authHandler) baseUsers(w http.ResponseWriter, r *http.Request) {
|
|||||||
if !allowMethod(w, r.Method, "GET") {
|
if !allowMethod(w, r.Method, "GET") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !hasRootAccess(sh.sec, r) {
|
if !hasRootAccess(sh.sec, r, sh.clientCertAuthEnabled) {
|
||||||
writeNoAuth(w, r)
|
writeNoAuth(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -365,7 +399,7 @@ func (sh *authHandler) forUser(w http.ResponseWriter, r *http.Request, user stri
|
|||||||
if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") {
|
if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !hasRootAccess(sh.sec, r) {
|
if !hasRootAccess(sh.sec, r, sh.clientCertAuthEnabled) {
|
||||||
writeNoAuth(w, r)
|
writeNoAuth(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -478,7 +512,7 @@ func (sh *authHandler) enableDisable(w http.ResponseWriter, r *http.Request) {
|
|||||||
if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") {
|
if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !hasWriteRootAccess(sh.sec, r) {
|
if !hasWriteRootAccess(sh.sec, r, sh.clientCertAuthEnabled) {
|
||||||
writeNoAuth(w, r)
|
writeNoAuth(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,13 @@
|
|||||||
package v2http
|
package v2http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -440,6 +444,32 @@ func mustAuthRequest(method, username, password string) *http.Request {
|
|||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func unauthedRequest(method string) *http.Request {
|
||||||
|
req, err := http.NewRequest(method, "path", strings.NewReader(""))
|
||||||
|
if err != nil {
|
||||||
|
panic("Cannot make request: " + err.Error())
|
||||||
|
}
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func tlsAuthedRequest(req *http.Request, certname string) *http.Request {
|
||||||
|
bytes, err := ioutil.ReadFile(fmt.Sprintf("testdata/%s.pem", certname))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
block, _ := pem.Decode(bytes)
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.TLS = &tls.ConnectionState{
|
||||||
|
VerifiedChains: [][]*x509.Certificate{{cert}},
|
||||||
|
}
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
func TestPrefixAccess(t *testing.T) {
|
func TestPrefixAccess(t *testing.T) {
|
||||||
var table = []struct {
|
var table = []struct {
|
||||||
key string
|
key string
|
||||||
@ -690,14 +720,161 @@ func TestPrefixAccess(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, tt := range table {
|
for i, tt := range table {
|
||||||
if tt.hasRoot != hasRootAccess(tt.store, tt.req) {
|
if tt.hasRoot != hasRootAccess(tt.store, tt.req, true) {
|
||||||
t.Errorf("#%d: hasRoot doesn't match (expected %v)", i, tt.hasRoot)
|
t.Errorf("#%d: hasRoot doesn't match (expected %v)", i, tt.hasRoot)
|
||||||
}
|
}
|
||||||
if tt.hasKeyPrefixAccess != hasKeyPrefixAccess(tt.store, tt.req, tt.key, false) {
|
if tt.hasKeyPrefixAccess != hasKeyPrefixAccess(tt.store, tt.req, tt.key, false, true) {
|
||||||
t.Errorf("#%d: hasKeyPrefixAccess doesn't match (expected %v)", i, tt.hasRoot)
|
t.Errorf("#%d: hasKeyPrefixAccess doesn't match (expected %v)", i, tt.hasRoot)
|
||||||
}
|
}
|
||||||
if tt.hasRecursiveAccess != hasKeyPrefixAccess(tt.store, tt.req, tt.key, true) {
|
if tt.hasRecursiveAccess != hasKeyPrefixAccess(tt.store, tt.req, tt.key, true, true) {
|
||||||
t.Errorf("#%d: hasRecursiveAccess doesn't match (expected %v)", i, tt.hasRoot)
|
t.Errorf("#%d: hasRecursiveAccess doesn't match (expected %v)", i, tt.hasRoot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUserFromClientCertificate(t *testing.T) {
|
||||||
|
witherror := &mockAuthStore{
|
||||||
|
users: map[string]*auth.User{
|
||||||
|
"user": {
|
||||||
|
User: "user",
|
||||||
|
Roles: []string{"root"},
|
||||||
|
Password: "password",
|
||||||
|
},
|
||||||
|
"basicauth": {
|
||||||
|
User: "basicauth",
|
||||||
|
Roles: []string{"root"},
|
||||||
|
Password: "password",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
roles: map[string]*auth.Role{
|
||||||
|
"root": {
|
||||||
|
Role: "root",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: errors.New(""),
|
||||||
|
}
|
||||||
|
|
||||||
|
noerror := &mockAuthStore{
|
||||||
|
users: map[string]*auth.User{
|
||||||
|
"user": {
|
||||||
|
User: "user",
|
||||||
|
Roles: []string{"root"},
|
||||||
|
Password: "password",
|
||||||
|
},
|
||||||
|
"basicauth": {
|
||||||
|
User: "basicauth",
|
||||||
|
Roles: []string{"root"},
|
||||||
|
Password: "password",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
roles: map[string]*auth.Role{
|
||||||
|
"root": {
|
||||||
|
Role: "root",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var table = []struct {
|
||||||
|
req *http.Request
|
||||||
|
userExists bool
|
||||||
|
store auth.Store
|
||||||
|
username string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// non tls request
|
||||||
|
req: unauthedRequest("GET"),
|
||||||
|
userExists: false,
|
||||||
|
store: witherror,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// cert with cn of existing user
|
||||||
|
req: tlsAuthedRequest(unauthedRequest("GET"), "user"),
|
||||||
|
userExists: true,
|
||||||
|
username: "user",
|
||||||
|
store: noerror,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// cert with cn of non-existing user
|
||||||
|
req: tlsAuthedRequest(unauthedRequest("GET"), "otheruser"),
|
||||||
|
userExists: false,
|
||||||
|
store: witherror,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range table {
|
||||||
|
user := userFromClientCertificate(tt.store, tt.req)
|
||||||
|
userExists := user != nil
|
||||||
|
|
||||||
|
if tt.userExists != userExists {
|
||||||
|
t.Errorf("#%d: userFromClientCertificate doesn't match (expected %v)", i, tt.userExists)
|
||||||
|
}
|
||||||
|
if user != nil && (tt.username != user.User) {
|
||||||
|
t.Errorf("#%d: userFromClientCertificate username doesn't match (expected %s, got %s)", i, tt.username, user.User)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserFromBasicAuth(t *testing.T) {
|
||||||
|
sec := &mockAuthStore{
|
||||||
|
users: map[string]*auth.User{
|
||||||
|
"user": {
|
||||||
|
User: "user",
|
||||||
|
Roles: []string{"root"},
|
||||||
|
Password: "password",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
roles: map[string]*auth.Role{
|
||||||
|
"root": {
|
||||||
|
Role: "root",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var table = []struct {
|
||||||
|
username string
|
||||||
|
req *http.Request
|
||||||
|
userExists bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// valid user, valid pass
|
||||||
|
username: "user",
|
||||||
|
req: mustAuthRequest("GET", "user", "password"),
|
||||||
|
userExists: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// valid user, bad pass
|
||||||
|
username: "user",
|
||||||
|
req: mustAuthRequest("GET", "user", "badpass"),
|
||||||
|
userExists: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// valid user, no pass
|
||||||
|
username: "user",
|
||||||
|
req: mustAuthRequest("GET", "user", ""),
|
||||||
|
userExists: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// missing user
|
||||||
|
username: "missing",
|
||||||
|
req: mustAuthRequest("GET", "missing", "badpass"),
|
||||||
|
userExists: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// no basic auth
|
||||||
|
req: unauthedRequest("GET"),
|
||||||
|
userExists: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range table {
|
||||||
|
user := userFromBasicAuth(sec, tt.req)
|
||||||
|
userExists := user != nil
|
||||||
|
|
||||||
|
if tt.userExists != userExists {
|
||||||
|
t.Errorf("#%d: userFromBasicAuth doesn't match (expected %v)", i, tt.userExists)
|
||||||
|
}
|
||||||
|
if user != nil && (tt.username != user.User) {
|
||||||
|
t.Errorf("#%d: userFromBasicAuth username doesn't match (expected %s, got %s)", i, tt.username, user.User)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
19
etcdserver/api/v2http/testdata/ca.pem
vendored
Normal file
19
etcdserver/api/v2http/testdata/ca.pem
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDEjCCAfqgAwIBAgIIYpX+8HgWGfkwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
|
||||||
|
AxMKZXRjZCB0ZXN0czAeFw0xNTExMjQwMzA1MDBaFw0yMDExMjIwMzA1MDBaMBUx
|
||||||
|
EzARBgNVBAMTCmV0Y2QgdGVzdHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
|
||||||
|
AoIBAQDa9PkwEwiBD8mB+VIKz5r5gRHnNF4Icj6T6R/RsdatecQe6vU0EU4FXtKZ
|
||||||
|
drWnCGlATyrQooqHpb+rDc7CUt3mXrIxrNkcGTMaesF7P0GWxVkyOGSjJMxGBv3e
|
||||||
|
bAZknBe4eLMi68L1aT/uYmxcp/B3L2mfdFtc1Gd6mYJpNm1PgilRyIrO0mY5ysIX
|
||||||
|
4WHCa3yudAv8HrFbQcw7l7OyKA6uSWg6h07lE3d5jw5YOly+hz0iaRtzhb4tJrYD
|
||||||
|
Lm1tehb0nnoLuW6yYblRSoyBVDT50MFVlyvW40Po5WkOXw/wnsnyxWRR4yqU23wq
|
||||||
|
quQU0HXJEBLFnT+KbLOQ0EAE35vXAgMBAAGjZjBkMA4GA1UdDwEB/wQEAwIBBjAS
|
||||||
|
BgNVHRMBAf8ECDAGAQH/AgECMB0GA1UdDgQWBBSbUCGB95ochDrbEZlzGGYuA7xu
|
||||||
|
xjAfBgNVHSMEGDAWgBSbUCGB95ochDrbEZlzGGYuA7xuxjANBgkqhkiG9w0BAQsF
|
||||||
|
AAOCAQEAardO/SGCu7Snz3YRBUinzpZEUFTFend+FJtBkxBXCao1RvTXg8PBMkza
|
||||||
|
LUsaR4mLsGoXLIbNCoIinvVG0QULYCZe11N3l1L0G2g5uhEM4MfJ2rwrMD0o17i+
|
||||||
|
nwNRRE3tfKAlWhYQg+4ye36kQVxASPniHjdQgjKYUFTNXdyG6DzuAclaVte9iVw6
|
||||||
|
cWl61fB2CZya3+uMtih8t/Kgl2KbMO2PvNByfnDjKmW+v58qHbXyoJZqnpvDn14+
|
||||||
|
p2Ox+AvvxYiEiUIvFdWy101QB7NJMCtdwq6oG6OvIOgXzLgitTFSq4kfWDfupQjW
|
||||||
|
iFoQ+vWmYhK5ld0nBaiz+JmHuemK7A==
|
||||||
|
-----END CERTIFICATE-----
|
20
etcdserver/api/v2http/testdata/otheruser.pem
vendored
Normal file
20
etcdserver/api/v2http/testdata/otheruser.pem
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDOTCCAiGgAwIBAgIINYpsso1f3SswDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
|
||||||
|
AxMKZXRjZCB0ZXN0czAeFw0xNTExMjQwMzA4MDBaFw0xNjExMjMwMzA4MDBaMBQx
|
||||||
|
EjAQBgNVBAMTCW90aGVydXNlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
|
||||||
|
ggEBAPOAUa5GblwIjHTEnox2c/Am9jV1TMvzBuVXxnp2UnNHMNwstAooFrEs/Z+d
|
||||||
|
ft5AOsooP6zVuM3eBQa4i9huJbVNDfPU2H94yA89jYfJYUgo7C838V6NjGsCCptQ
|
||||||
|
WzkKPNlDbT9xA/7XpIUJ2WltuYDRrjWq8pXQONqTjcg5n4l0JO8xdHJHRUkFQ76F
|
||||||
|
1npXeLndgGaP11lqzpYlglEGi5URhzAT1xxQ0hLSe8WNmiCxxkq++C8Gx4sPg9mX
|
||||||
|
M94aoJDzZSnoaqDxckbP/7Q0ZKe/fVdCFkd5+jqT4Mt7hwmz9jTCHcVnAz4EKI+t
|
||||||
|
rbWgbCfMK6013GotXz7InStVe+MCAwEAAaOBjTCBijAOBgNVHQ8BAf8EBAMCBaAw
|
||||||
|
HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYD
|
||||||
|
VR0OBBYEFFwMmf+pnaejmri6y1T+lfU+MBq/MB8GA1UdIwQYMBaAFJtQIYH3mhyE
|
||||||
|
OtsRmXMYZi4DvG7GMAsGA1UdEQQEMAKCADANBgkqhkiG9w0BAQsFAAOCAQEACOn6
|
||||||
|
mec29MTMGPt/EPOmSyhvTKSwH+5YWjCbyUFeoB8puxrJlIphK4mvT+sXp2wzno89
|
||||||
|
FVCliO/rJurdErKvyOjlK1QrVGPYIt7Wz9ssAfvlwCyBM8PqgEG8dJN9aAkf2h4r
|
||||||
|
Ye+hBh1y6Nnataf7lxe9mqAOvD/7wVIgzjCnMD1q5QSY2Mln3HwVQXtbZFbY363Z
|
||||||
|
X9Fk3PUpjJSX9jbEz9kIlT8AJAdxl6GB8Z9B8PrA8qf4Bhk15ICRHxb67EhDrGWV
|
||||||
|
8q7ArU2XBqs/+GWpUIMoGKNZv+K+/SksZK1KnzaUvApUCJzt+ac+p8HOgMdvDRgr
|
||||||
|
GfVVJqcZgyEmeczy0A==
|
||||||
|
-----END CERTIFICATE-----
|
20
etcdserver/api/v2http/testdata/user.pem
vendored
Normal file
20
etcdserver/api/v2http/testdata/user.pem
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDNDCCAhygAwIBAgIIcQ0DAfgevocwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
|
||||||
|
AxMKZXRjZCB0ZXN0czAeFw0xNTExMjQwMzA4MDBaFw0xNjExMjMwMzA4MDBaMA8x
|
||||||
|
DTALBgNVBAMTBHVzZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD0
|
||||||
|
+3Lm1SmUJJLufaFTYz+e5qyQEshNRyeAhXIeZ1aw+yBjslXGZQ3/uGOwnOnGqUeA
|
||||||
|
Nidc9ty4NsK6RVppHlezUrBnpl4hws8vHWFKZpU2R6kKL8EYLmg+iVqEBj7XqfAp
|
||||||
|
8bJqqZI3KOqLXpRH55mA69KP7VEK9ngTVR/tERSrUPT8jcjwbvhSOqD8Qk07BUDR
|
||||||
|
6RpDr94Mnaf+fMGG36Sh7iUl+i4Oh6FFar+7+b0+5Bhs2/6uVsK4A1Z3jqqfSQH8
|
||||||
|
q8Wf5h9Ka4aqGSw4ia5G3Uw7Jsl2aDgpJ7uwJo1k8SclbMYnYdhZuo+U+esY/Fai
|
||||||
|
YdbjG+AroZ+y9TB8bMlHAgMBAAGjgY0wgYowDgYDVR0PAQH/BAQDAgWgMB0GA1Ud
|
||||||
|
JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW
|
||||||
|
BBRuTt0lJIVKYaz76aSxl/MQOLRwfDAfBgNVHSMEGDAWgBSbUCGB95ochDrbEZlz
|
||||||
|
GGYuA7xuxjALBgNVHREEBDACggAwDQYJKoZIhvcNAQELBQADggEBABLRWZm+Lgjs
|
||||||
|
c5qDXbgOJW2pR630syY8ixR9c6HvzPVJim8mFioMX+xrlbOC6BmOUlOb9j83bTKn
|
||||||
|
aOg/0xlpxNbd8QYzgRxZmHZLULPdiNeeRvIzsrzrH88+inrmZhRXRVcHjdO6CG6t
|
||||||
|
hCdDdRiNU6GkF7dPna0xNcEOKe2wUfzd1ZtKOqzi1w+fKjSeMplZomeWgP4WRvkh
|
||||||
|
JJ/0ujlMMckgyTxRh8EEaJ35OnpXX7EdipoWhOMmiUnlPqye2icC8Y+CMdZsrod6
|
||||||
|
nkoEQnXDCLf/Iv0qj7B9iKbxn7t3QDVxY4UILUReDuD8yrGULlGOl//aY/T3pkZ6
|
||||||
|
R5trduZhI3o=
|
||||||
|
-----END CERTIFICATE-----
|
@ -56,6 +56,9 @@ type ServerConfig struct {
|
|||||||
StrictReconfigCheck bool
|
StrictReconfigCheck bool
|
||||||
|
|
||||||
EnablePprof bool
|
EnablePprof bool
|
||||||
|
|
||||||
|
// ClientCertAuthEnabled is true when cert has been signed by the client CA.
|
||||||
|
ClientCertAuthEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyBootstrap sanity-checks the initial config for bootstrap case
|
// VerifyBootstrap sanity-checks the initial config for bootstrap case
|
||||||
|
Loading…
x
Reference in New Issue
Block a user