Files
releases/plugins/rest/rest_auth.go
T
Ashutosh Narkar a5be4f49a2 plugins: Add support to specify bearer token path
This change updates the bearer token config to allow clients to specify
a path to the token. With this refreshing tokens becomes easier as OPA
will now reload the token from file.

Fixes #2241

Signed-off-by: Ashutosh Narkar <anarkar4387@gmail.com>
2020-04-10 17:29:29 -07:00

238 lines
6.7 KiB
Go

// Copyright 2019 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package rest
import (
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"strings"
"time"
"github.com/sirupsen/logrus"
)
// defaultTLSConfig defines standard TLS configurations based on the Config
func defaultTLSConfig(c Config) (*tls.Config, error) {
t := &tls.Config{}
url, err := url.Parse(c.URL)
if err != nil {
return nil, err
}
if url.Scheme == "https" {
t.InsecureSkipVerify = c.AllowInsureTLS
}
return t, nil
}
// defaultRoundTripperClient is a reasonable set of defaults for HTTP auth plugins
func defaultRoundTripperClient(t *tls.Config) *http.Client {
// Ensure we use a http.Transport with proper settings: the zero values are not
// a good choice, as they cause leaking connections:
// https://github.com/golang/go/issues/19620
//
// Also, there's no simple way to copy the values from http.DefaultTransport,
// see https://github.com/golang/go/issues/26013. Hence, we copy the settings
// used in the golang sources,
// https://github.com/golang/go/blob/5fae09b7386de26db59a1184f62fc7b22ec7667b/src/net/http/transport.go#L42-L53
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
var tr http.RoundTripper = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: t,
}
// copy, we don't want to alter the default client's Transport
c := *http.DefaultClient
c.Transport = tr
return &c
}
// defaultAuthPlugin represents baseline 'no auth' behavior if no alternative plugin is specified for a service
type defaultAuthPlugin struct{}
func (ap *defaultAuthPlugin) NewClient(c Config) (*http.Client, error) {
t, err := defaultTLSConfig(c)
if err != nil {
return nil, err
}
return defaultRoundTripperClient(t), nil
}
func (ap *defaultAuthPlugin) Prepare(req *http.Request) error {
return nil
}
// bearerAuthPlugin represents authentication via a bearer token in the HTTP Authorization header
type bearerAuthPlugin struct {
Token string `json:"token"`
TokenPath string `json:"token_path"`
Scheme string `json:"scheme,omitempty"`
}
func (ap *bearerAuthPlugin) NewClient(c Config) (*http.Client, error) {
t, err := defaultTLSConfig(c)
if err != nil {
return nil, err
}
if ap.Token != "" && ap.TokenPath != "" {
return nil, errors.New("invalid config: specify a value for either the \"token\" or \"token_path\" field")
}
if ap.Scheme == "" {
ap.Scheme = "Bearer"
}
return defaultRoundTripperClient(t), nil
}
func (ap *bearerAuthPlugin) Prepare(req *http.Request) error {
token := ap.Token
if ap.TokenPath != "" {
bytes, err := ioutil.ReadFile(ap.TokenPath)
if err != nil {
return err
}
token = strings.TrimSpace(string(bytes))
}
req.Header.Add("Authorization", fmt.Sprintf("%v %v", ap.Scheme, token))
return nil
}
// clientTLSAuthPlugin represents authentication via client certificate on a TLS connection
type clientTLSAuthPlugin struct {
Cert string `json:"cert"`
PrivateKey string `json:"private_key"`
PrivateKeyPassphrase string `json:"private_key_passphrase,omitempty"`
}
func (ap *clientTLSAuthPlugin) NewClient(c Config) (*http.Client, error) {
tlsConfig, err := defaultTLSConfig(c)
if err != nil {
return nil, err
}
if ap.Cert == "" {
return nil, errors.New("client certificate is needed when client TLS is enabled")
}
if ap.PrivateKey == "" {
return nil, errors.New("private key is needed when client TLS is enabled")
}
var keyPEMBlock []byte
data, err := ioutil.ReadFile(ap.PrivateKey)
if err != nil {
return nil, err
}
block, _ := pem.Decode(data)
if block == nil {
return nil, errors.New("PEM data could not be found")
}
if x509.IsEncryptedPEMBlock(block) {
if ap.PrivateKeyPassphrase == "" {
return nil, errors.New("client certificate passphrase is need, because the certificate is password encrypted")
}
block, err := x509.DecryptPEMBlock(block, []byte(ap.PrivateKeyPassphrase))
if err != nil {
return nil, err
}
key, err := x509.ParsePKCS8PrivateKey(block)
if err != nil {
key, err = x509.ParsePKCS1PrivateKey(block)
if err != nil {
return nil, fmt.Errorf("private key should be a PEM or plain PKCS1 or PKCS8; parse error: %v", err)
}
}
rsa, ok := key.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("private key is invalid")
}
keyPEMBlock = pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(rsa),
},
)
} else {
keyPEMBlock = data
}
certPEMBlock, err := ioutil.ReadFile(ap.Cert)
if err != nil {
return nil, err
}
cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
client := defaultRoundTripperClient(tlsConfig)
return client, nil
}
func (ap *clientTLSAuthPlugin) Prepare(req *http.Request) error {
return nil
}
// awsSigningAuthPlugin represents authentication using AWS V4 HMAC signing in the Authorization header
type awsSigningAuthPlugin struct {
AWSEnvironmentCredentials *awsEnvironmentCredentialService `json:"environment_credentials,omitempty"`
AWSMetadataCredentials *awsMetadataCredentialService `json:"metadata_credentials,omitempty"`
}
func (ap *awsSigningAuthPlugin) awsCredentialService() awsCredentialService {
if ap.AWSEnvironmentCredentials != nil {
return ap.AWSEnvironmentCredentials
}
return ap.AWSMetadataCredentials
}
func (ap *awsSigningAuthPlugin) NewClient(c Config) (*http.Client, error) {
t, err := defaultTLSConfig(c)
if err != nil {
return nil, err
}
if (ap.AWSEnvironmentCredentials == nil && ap.AWSMetadataCredentials == nil) ||
(ap.AWSEnvironmentCredentials != nil && ap.AWSMetadataCredentials != nil) {
return nil, errors.New("exactly one AWS credential service must be specified when S3 signing is enabled")
}
if ap.AWSMetadataCredentials != nil {
if ap.AWSMetadataCredentials.RegionName == "" {
return nil, errors.New("at least aws_region must be specified for AWS metadata credential service")
}
}
return defaultRoundTripperClient(t), nil
}
func (ap *awsSigningAuthPlugin) Prepare(req *http.Request) error {
logrus.Debug("Signing request with AWS credentials.")
err := signV4(req, ap.awsCredentialService(), time.Now())
return err
}