зеркало из https://github.com/Azure/ARO-RP.git
Admin clientauthorizer implementation
This commit is contained in:
Родитель
e1692ab0ad
Коммит
35632b5048
|
@ -40,12 +40,29 @@ func rp(ctx context.Context, log *logrus.Entry) error {
|
|||
uuid := uuid.NewV4().String()
|
||||
log.Printf("uuid %s", uuid)
|
||||
|
||||
env, err := env.NewEnv(ctx, log)
|
||||
_env, err := env.NewEnv(ctx, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m, err := statsd.New(ctx, log.WithField("component", "metrics"), env, os.Getenv("MDM_ACCOUNT"), os.Getenv("MDM_NAMESPACE"))
|
||||
if _, ok := _env.(env.Dev); !ok {
|
||||
for _, key := range []string{
|
||||
"MDM_ACCOUNT",
|
||||
"MDM_NAMESPACE",
|
||||
"ADMIN_API_CLIENT_CERT_COMMON_NAME",
|
||||
} {
|
||||
if _, found := os.LookupEnv(key); !found {
|
||||
return fmt.Errorf("environment variable %q unset", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = _env.InitializeAuthorizers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m, err := statsd.New(ctx, log.WithField("component", "metrics"), _env, os.Getenv("MDM_ACCOUNT"), os.Getenv("MDM_NAMESPACE"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -53,22 +70,22 @@ func rp(ctx context.Context, log *logrus.Entry) error {
|
|||
tracing.Register(azure.New(m))
|
||||
metrics.Register(k8s.NewLatency(m), k8s.NewResult(m))
|
||||
|
||||
cipher, err := encryption.NewXChaCha20Poly1305(ctx, env)
|
||||
cipher, err := encryption.NewXChaCha20Poly1305(ctx, _env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db, err := database.NewDatabase(ctx, log.WithField("component", "database"), env, m, cipher, uuid)
|
||||
db, err := database.NewDatabase(ctx, log.WithField("component", "database"), _env, m, cipher, uuid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := frontend.NewFrontend(ctx, log.WithField("component", "frontend"), env, db, api.APIs, m)
|
||||
f, err := frontend.NewFrontend(ctx, log.WithField("component", "frontend"), _env, db, api.APIs, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := backend.NewBackend(ctx, log.WithField("component", "backend"), env, db, m)
|
||||
b, err := backend.NewBackend(ctx, log.WithField("component", "backend"), _env, db, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ type dev struct {
|
|||
proxyClientKey *rsa.PrivateKey
|
||||
}
|
||||
|
||||
func newDev(ctx context.Context, log *logrus.Entry, instancemetadata instancemetadata.InstanceMetadata, armClientAuthorizer, adminClientAuthorizer clientauthorizer.ClientAuthorizer) (*dev, error) {
|
||||
func newDev(ctx context.Context, log *logrus.Entry, instancemetadata instancemetadata.InstanceMetadata) (*dev, error) {
|
||||
for _, key := range []string{
|
||||
"AZURE_ARM_CLIENT_ID",
|
||||
"AZURE_ARM_CLIENT_SECRET",
|
||||
|
@ -97,7 +97,7 @@ func newDev(ctx context.Context, log *logrus.Entry, instancemetadata instancemet
|
|||
roleassignments: authorization.NewRoleAssignmentsClient(instancemetadata.SubscriptionID(), armAuthorizer),
|
||||
}
|
||||
|
||||
d.prod, err = newProd(ctx, log, instancemetadata, armClientAuthorizer, adminClientAuthorizer)
|
||||
d.prod, err = newProd(ctx, log, instancemetadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -151,6 +151,12 @@ func newDev(ctx context.Context, log *logrus.Entry, instancemetadata instancemet
|
|||
return d, nil
|
||||
}
|
||||
|
||||
func (d *dev) InitializeAuthorizers() error {
|
||||
d.armClientAuthorizer = clientauthorizer.NewAll()
|
||||
d.adminClientAuthorizer = clientauthorizer.NewAll()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dev) DatabaseName() string {
|
||||
return os.Getenv("DATABASE_NAME")
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"context"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -22,6 +21,7 @@ import (
|
|||
type Interface interface {
|
||||
instancemetadata.InstanceMetadata
|
||||
|
||||
InitializeAuthorizers() error
|
||||
ArmClientAuthorizer() clientauthorizer.ClientAuthorizer
|
||||
AdminClientAuthorizer() clientauthorizer.ClientAuthorizer
|
||||
ClustersGenevaLoggingConfigVersion() string
|
||||
|
@ -44,7 +44,7 @@ type Interface interface {
|
|||
func NewEnv(ctx context.Context, log *logrus.Entry) (Interface, error) {
|
||||
if strings.ToLower(os.Getenv("RP_MODE")) == "development" {
|
||||
log.Warn("running in development mode")
|
||||
return newDev(ctx, log, instancemetadata.NewDev(), clientauthorizer.NewAll(), clientauthorizer.NewAll())
|
||||
return newDev(ctx, log, instancemetadata.NewDev())
|
||||
}
|
||||
|
||||
im, err := instancemetadata.NewProd()
|
||||
|
@ -54,17 +54,8 @@ func NewEnv(ctx context.Context, log *logrus.Entry) (Interface, error) {
|
|||
|
||||
if strings.ToLower(os.Getenv("RP_MODE")) == "int" {
|
||||
log.Warn("running in int mode")
|
||||
return newInt(ctx, log, im, clientauthorizer.NewARM(log), clientauthorizer.NewAdmin(log))
|
||||
return newInt(ctx, log, im)
|
||||
}
|
||||
|
||||
for _, key := range []string{
|
||||
"MDM_ACCOUNT",
|
||||
"MDM_NAMESPACE",
|
||||
} {
|
||||
if _, found := os.LookupEnv(key); !found {
|
||||
return nil, fmt.Errorf("environment variable %q unset", key)
|
||||
}
|
||||
}
|
||||
|
||||
return newProd(ctx, log, im, clientauthorizer.NewARM(log), clientauthorizer.NewAdmin(log))
|
||||
return newProd(ctx, log, im)
|
||||
}
|
||||
|
|
|
@ -8,12 +8,11 @@ import (
|
|||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/Azure/ARO-RP/pkg/util/clientauthorizer"
|
||||
"github.com/Azure/ARO-RP/pkg/util/instancemetadata"
|
||||
)
|
||||
|
||||
func newInt(ctx context.Context, log *logrus.Entry, instancemetadata instancemetadata.InstanceMetadata, armClientAuthorizer, adminClientAuthorizer clientauthorizer.ClientAuthorizer) (*prod, error) {
|
||||
p, err := newProd(ctx, log, instancemetadata, armClientAuthorizer, adminClientAuthorizer)
|
||||
func newInt(ctx context.Context, log *logrus.Entry, instancemetadata instancemetadata.InstanceMetadata) (*prod, error) {
|
||||
p, err := newProd(ctx, log, instancemetadata)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -52,23 +53,25 @@ type prod struct {
|
|||
clustersGenevaLoggingPrivateKey *rsa.PrivateKey
|
||||
clustersGenevaLoggingConfigVersion string
|
||||
clustersGenevaLoggingEnvironment string
|
||||
|
||||
log *logrus.Entry
|
||||
}
|
||||
|
||||
func newProd(ctx context.Context, log *logrus.Entry, instancemetadata instancemetadata.InstanceMetadata, armClientAuthorizer, adminClientAuthorizer clientauthorizer.ClientAuthorizer) (*prod, error) {
|
||||
func newProd(ctx context.Context, log *logrus.Entry, instancemetadata instancemetadata.InstanceMetadata) (*prod, error) {
|
||||
kvAuthorizer, err := auth.NewAuthorizerFromEnvironmentWithResource(azure.PublicCloud.ResourceIdentifiers.KeyVault)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &prod{
|
||||
InstanceMetadata: instancemetadata,
|
||||
armClientAuthorizer: armClientAuthorizer,
|
||||
adminClientAuthorizer: adminClientAuthorizer,
|
||||
InstanceMetadata: instancemetadata,
|
||||
|
||||
keyvault: basekeyvault.New(kvAuthorizer),
|
||||
|
||||
clustersGenevaLoggingEnvironment: "DiagnosticsProd",
|
||||
clustersGenevaLoggingConfigVersion: "2.1",
|
||||
|
||||
log: log,
|
||||
}
|
||||
|
||||
rpAuthorizer, err := auth.NewAuthorizerFromEnvironment()
|
||||
|
@ -116,6 +119,22 @@ func newProd(ctx context.Context, log *logrus.Entry, instancemetadata instanceme
|
|||
return p, nil
|
||||
}
|
||||
|
||||
func (p *prod) InitializeAuthorizers() error {
|
||||
p.armClientAuthorizer = clientauthorizer.NewARM(p.log)
|
||||
|
||||
adminClientAuthorizer, err := clientauthorizer.NewAdmin(
|
||||
p.log,
|
||||
"/etc/aro-rp/admin-ca-bundle.pem",
|
||||
os.Getenv("ADMIN_API_CLIENT_CERT_COMMON_NAME"),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.adminClientAuthorizer = adminClientAuthorizer
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *prod) ArmClientAuthorizer() clientauthorizer.ClientAuthorizer {
|
||||
return p.armClientAuthorizer
|
||||
}
|
||||
|
|
|
@ -5,23 +5,90 @@ package clientauthorizer
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type admin struct {
|
||||
log *logrus.Entry
|
||||
// NewAdmin creates a new instance of ClientAuthorizer to be used with Admin API.
|
||||
// This authorizer allows connections only if they
|
||||
// contain a valid client certificate signed by `caBundlePath` and
|
||||
// the client certificate's CommonName equals `clientCertCommonName`.
|
||||
func NewAdmin(log *logrus.Entry, caBundlePath, clientCertCommonName string) (ClientAuthorizer, error) {
|
||||
authorizer := &admin{
|
||||
clientCertCommonName: clientCertCommonName,
|
||||
|
||||
log: log,
|
||||
readFile: ioutil.ReadFile,
|
||||
}
|
||||
|
||||
err := authorizer.readCABundle(caBundlePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return authorizer, nil
|
||||
}
|
||||
|
||||
func NewAdmin(log *logrus.Entry) ClientAuthorizer {
|
||||
return &admin{
|
||||
log: log,
|
||||
type admin struct {
|
||||
roots *x509.CertPool
|
||||
clientCertCommonName string
|
||||
|
||||
log *logrus.Entry
|
||||
readFile func(filename string) ([]byte, error)
|
||||
}
|
||||
|
||||
func (a *admin) readCABundle(caBundlePath string) error {
|
||||
caBundle, err := a.readFile(caBundlePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
ok := roots.AppendCertsFromPEM(caBundle)
|
||||
if !ok {
|
||||
return fmt.Errorf("can not decode admin CA bundle from %s", caBundlePath)
|
||||
}
|
||||
|
||||
a.roots = roots
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *admin) IsAuthorized(cs *tls.ConnectionState) bool {
|
||||
a.log.Print("Admin auth is not implemented yet")
|
||||
return false
|
||||
if a.roots == nil {
|
||||
// Should never happen
|
||||
a.log.Error("no CA certificate")
|
||||
return false
|
||||
}
|
||||
|
||||
if cs == nil || len(cs.PeerCertificates) == 0 {
|
||||
a.log.Debug("no certificate present for the connection")
|
||||
return false
|
||||
}
|
||||
|
||||
verifyOpts := x509.VerifyOptions{
|
||||
Roots: a.roots,
|
||||
Intermediates: x509.NewCertPool(),
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
|
||||
for _, cert := range cs.PeerCertificates[1:] {
|
||||
verifyOpts.Intermediates.AddCert(cert)
|
||||
}
|
||||
|
||||
_, err := cs.PeerCertificates[0].Verify(verifyOpts)
|
||||
if err != nil {
|
||||
a.log.Debug(err)
|
||||
return false
|
||||
}
|
||||
|
||||
if cs.PeerCertificates[0].Subject.CommonName != a.clientCertCommonName {
|
||||
a.log.Debug("unexpected common name in the admin API client certificate")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *admin) IsReady() bool {
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
package clientauthorizer
|
||||
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the Apache License 2.0.
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
utiltls "github.com/Azure/ARO-RP/pkg/util/tls"
|
||||
)
|
||||
|
||||
func TestAdminClientAuthorizer(t *testing.T) {
|
||||
caBundlePath := "/fake/path/to/ca/cert.pem"
|
||||
log := logrus.NewEntry(logrus.StandardLogger())
|
||||
|
||||
validCaKey, validCaCerts, err := utiltls.GenerateKeyAndCertificate("validca", nil, nil, true, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
cs func() (*tls.ConnectionState, error)
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "allow: single valid client certificate",
|
||||
want: true,
|
||||
cs: func() (*tls.ConnectionState, error) {
|
||||
_, validSingleClientCert, err := utiltls.GenerateKeyAndCertificate("validclient", validCaKey, validCaCerts[0], false, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tls.ConnectionState{
|
||||
PeerCertificates: validSingleClientCert,
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "allow: valid client certificate with intermediates",
|
||||
want: true,
|
||||
cs: func() (*tls.ConnectionState, error) {
|
||||
validIntermediateCaKey, validIntermediateCaCerts, err := utiltls.GenerateKeyAndCertificate("valid-intermediate-ca", validCaKey, validCaCerts[0], true, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, validCertWithIntermediates, err := utiltls.GenerateKeyAndCertificate("validclient", validIntermediateCaKey, validIntermediateCaCerts[0], false, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validCertWithIntermediates = append(validCertWithIntermediates, validIntermediateCaCerts...)
|
||||
|
||||
return &tls.ConnectionState{
|
||||
PeerCertificates: validCertWithIntermediates,
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deny: valid certificate with unexpected common name",
|
||||
cs: func() (*tls.ConnectionState, error) {
|
||||
_, invalidCommonNameClientCert, err := utiltls.GenerateKeyAndCertificate("invalidclient", validCaKey, validCaCerts[0], false, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tls.ConnectionState{
|
||||
PeerCertificates: invalidCommonNameClientCert,
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deny: certificate with unexpected key usage",
|
||||
cs: func() (*tls.ConnectionState, error) {
|
||||
_, invalidKeyUsagesCert, err := utiltls.GenerateKeyAndCertificate("validclient", validCaKey, validCaCerts[0], false, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tls.ConnectionState{
|
||||
PeerCertificates: invalidKeyUsagesCert,
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deny: matching common name, but unexpected ca",
|
||||
cs: func() (*tls.ConnectionState, error) {
|
||||
invalidCaKey, invalidCaCerts, err := utiltls.GenerateKeyAndCertificate("invalidca", nil, nil, true, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, invalidSigningCa, err := utiltls.GenerateKeyAndCertificate("validclient", invalidCaKey, invalidCaCerts[0], false, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tls.ConnectionState{
|
||||
PeerCertificates: invalidSigningCa,
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deny: connection without client certificates",
|
||||
cs: func() (*tls.ConnectionState, error) {
|
||||
return &tls.ConnectionState{}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deny: nil connection state",
|
||||
cs: func() (*tls.ConnectionState, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
adminAuthorizer := &admin{
|
||||
clientCertCommonName: "validclient",
|
||||
|
||||
log: log,
|
||||
readFile: func(filename string) ([]byte, error) {
|
||||
if filename != caBundlePath {
|
||||
t.Fatal(filename)
|
||||
return nil, errors.New("noop")
|
||||
}
|
||||
|
||||
return utiltls.CertAsBytes(validCaCerts...)
|
||||
},
|
||||
}
|
||||
err := adminAuthorizer.readCABundle(caBundlePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cs, err := tt.cs()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
result := adminAuthorizer.IsAuthorized(cs)
|
||||
if result != tt.want {
|
||||
t.Error(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminClientAuthorizerReadCABundle(t *testing.T) {
|
||||
validCaKey, validCaCerts, err := utiltls.GenerateKeyAndCertificate("validca", nil, nil, true, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, validClientCert, err := utiltls.GenerateKeyAndCertificate("validclient", validCaKey, validCaCerts[0], false, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cs := &tls.ConnectionState{PeerCertificates: validClientCert}
|
||||
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
readFile func(string) ([]byte, error)
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "valid ca cert",
|
||||
readFile: func(string) ([]byte, error) {
|
||||
return utiltls.CertAsBytes(validCaCerts...)
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "error reading ca cert file",
|
||||
readFile: func(string) ([]byte, error) {
|
||||
return nil, errors.New("noop")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error decoding ca cert file",
|
||||
readFile: func(string) ([]byte, error) {
|
||||
return []byte("invalid-ca-cert"), nil
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
adminAuthorizer := &admin{
|
||||
clientCertCommonName: "validclient",
|
||||
|
||||
log: logrus.NewEntry(logrus.StandardLogger()),
|
||||
readFile: tt.readFile,
|
||||
}
|
||||
|
||||
if adminAuthorizer.IsAuthorized(cs) {
|
||||
t.Error("expected deny before the readCABundle call")
|
||||
}
|
||||
|
||||
readCABundleErr := adminAuthorizer.readCABundle("/fake/path/to/ca/cert.pem")
|
||||
IsAuthorized := adminAuthorizer.IsAuthorized(cs)
|
||||
|
||||
if tt.want {
|
||||
if readCABundleErr != nil {
|
||||
t.Error(readCABundleErr)
|
||||
}
|
||||
if !IsAuthorized {
|
||||
t.Error("expected to allow")
|
||||
}
|
||||
} else {
|
||||
if readCABundleErr == nil {
|
||||
t.Error("expected an error")
|
||||
}
|
||||
if IsAuthorized {
|
||||
t.Error("expected deny")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче