Merge pull request #5991 from gyuho/manual

v2http: client certificate auth via common name
This commit is contained in:
Gyu-Ho Lee 2016-07-21 08:02:17 -07:00 committed by GitHub
commit de638a5e4d
10 changed files with 399 additions and 72 deletions

View File

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

View File

@ -149,13 +149,14 @@ type etcdProcessClusterConfig struct {
snapCount int // default is 10000 snapCount int // default is 10000
clientTLS clientConnType clientTLS clientConnType
isPeerTLS bool clientCertAuthEnabled bool
isPeerAutoTLS bool isPeerTLS bool
isClientAutoTLS bool isPeerAutoTLS bool
forceNewCluster bool isClientAutoTLS bool
initialToken string forceNewCluster bool
quotaBackendBytes int64 initialToken string
quotaBackendBytes int64
} }
// newEtcdProcessCluster launches a new cluster from etcd processes, returning // newEtcdProcessCluster launches a new cluster from etcd processes, returning
@ -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")
}
} }
} }

View File

@ -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 {

View File

@ -65,11 +65,12 @@ func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http
sec := auth.NewStore(server, timeout) sec := auth.NewStore(server, timeout)
kh := &keysHandler{ kh := &keysHandler{
sec: sec, sec: sec,
server: server, server: server,
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{
@ -89,8 +91,9 @@ 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()
@ -132,11 +135,12 @@ func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http
} }
type keysHandler struct { type keysHandler struct {
sec auth.Store sec auth.Store
server etcdserver.Server server etcdserver.Server
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
} }
@ -199,18 +203,19 @@ func (h *deprecatedMachinesHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
} }
type membersHandler struct { type membersHandler struct {
sec auth.Store sec auth.Store
server etcdserver.Server server etcdserver.Server
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
} }

View File

@ -26,18 +26,56 @@ 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
return false if r.Header.Get("Authorization") == "" && clientCertAuthEnabled {
} rootUser = userFromClientCertificate(sec, r)
rootUser, err := sec.GetUser(username) if rootUser == nil {
if err != nil { return false
return false }
} else {
rootUser = userFromBasicAuth(sec, r)
if rootUser == nil {
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") == "" {
plog.Warningf("auth: no authorization provided, checking guest access") var user *auth.User
return hasGuestAccess(sec, r, key) if r.Header.Get("Authorization") == "" && clientCertAuthEnabled {
} user = userFromClientCertificate(sec, r)
username, password, ok := r.BasicAuth() if user == nil {
if !ok { plog.Warningf("auth: no authorization provided, checking guest access")
plog.Warningf("auth: malformed basic auth encoding") return hasGuestAccess(sec, r, key)
return false }
} } else {
user, err := sec.GetUser(username) user = userFromBasicAuth(sec, r)
if err != nil { if user == nil {
plog.Warningf("auth: no such user: %s.", username) return false
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
} }

View File

@ -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
View 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-----

View 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
View 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-----

View File

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