Skip to content

Commit

Permalink
Authentication processor 3/4 - Add implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Juraci Paixão Kröhling <[email protected]>
  • Loading branch information
jpkrohling committed Sep 18, 2020
1 parent f2c672e commit 007f9f9
Show file tree
Hide file tree
Showing 7 changed files with 951 additions and 4 deletions.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/census-instrumentation/opencensus-proto v0.3.0
github.com/client9/misspell v0.3.4
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/davecgh/go-spew v1.1.1
github.com/evanphx/json-patch v4.5.0+incompatible // indirect
github.com/go-kit/kit v0.10.0
Expand Down Expand Up @@ -37,6 +38,7 @@ require (
github.com/orijtech/prometheus-go-metrics-exporter v0.0.5
github.com/ory/go-acc v0.2.6
github.com/pavius/impi v0.0.3
github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f // indirect
github.com/prometheus/client_golang v1.7.1
github.com/prometheus/common v0.13.0
github.com/prometheus/prometheus v1.8.2-0.20200827201422-1195cc24e3c8
Expand All @@ -62,6 +64,7 @@ require (
google.golang.org/grpc v1.32.0
google.golang.org/grpc/examples v0.0.0-20200728065043-dfc0c05b2da9 // indirect
google.golang.org/protobuf v1.25.0
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/yaml.v2 v2.3.0
honnef.co/go/tools v0.0.1-2020.1.5
)
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
Expand All @@ -174,6 +176,7 @@ github.com/daixiang0/gci v0.2.4 h1:BUCKk5nlK2m+kRIsoj+wb/5hazHvHeZieBKWd9Afa8Q=
github.com/daixiang0/gci v0.2.4/go.mod h1:+AV8KmHTGxxwp/pY84TLQfFKp2vuKXXJVzF3kD/hfR4=
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denis-tingajkin/go-header v0.3.1/go.mod h1:sq/2IxMhaZX+RRcgHfCRx/m0M5na0fBt4/CRe7Lrji0=
github.com/dgraph-io/badger v1.5.3/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
Expand Down Expand Up @@ -384,6 +387,7 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
Expand Down Expand Up @@ -748,9 +752,12 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f h1:JDEmUDtyiLMyMlFwiaDOv2hxUp35497fkwePcLeV7j4=
github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ=
github.com/prometheus/alertmanager v0.21.0/go.mod h1:h7tJ81NA0VLWvWEayi1QltevFkLF3KxmC/malTcT8Go=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
Expand Down Expand Up @@ -884,6 +891,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tcnksm/ghr v0.13.0/go.mod h1:tcp6tzbRYE0LqFSG7ykXP/BVG1/2BkX6aIn9FFV1mIQ=
Expand Down Expand Up @@ -979,6 +987,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
Expand Down Expand Up @@ -1061,6 +1070,7 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down Expand Up @@ -1136,6 +1146,7 @@ golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fq
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
Expand Down Expand Up @@ -1333,6 +1344,8 @@ gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuv
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
Expand Down
3 changes: 1 addition & 2 deletions internal/auth/authenticator.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (
var (
errNoOIDCProvided = errors.New("no OIDC information provided")
errMetadataNotFound = errors.New("no request metadata found")
errNotImplemented = errors.New("not implemented")
defaultAttribute = "authorization"
)

Expand Down Expand Up @@ -63,7 +62,7 @@ func New(cfg configauth.Authentication) (Authenticator, error) {
cfg.Attribute = defaultAttribute
}

return nil, errNotImplemented
return newOIDCAuthenticator(cfg)
}

func defaultUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, authenticate authenticateFunc) (interface{}, error) {
Expand Down
4 changes: 2 additions & 2 deletions internal/auth/authenticator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ func TestNew(t *testing.T) {
})

// verify
assert.Nil(t, p)
assert.Equal(t, errNotImplemented, err)
assert.NotNil(t, p)
assert.NoError(t, err)
}

func TestMissingOIDC(t *testing.T) {
Expand Down
247 changes: 247 additions & 0 deletions internal/auth/oidc_authenticator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package auth

import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"strings"
"time"

"github.com/coreos/go-oidc"
"google.golang.org/grpc"

"go.opentelemetry.io/collector/config/configauth"
)

type oidcAuthenticator struct {
attribute string
config configauth.OIDC
provider *oidc.Provider
verifier *oidc.IDTokenVerifier

unaryInterceptor unaryInterceptorFunc
streamInterceptor streamInterceptorFunc
}

var (
_ Authenticator = (*oidcAuthenticator)(nil)

errNoClientIDProvided = errors.New("no ClientID provided for the OIDC configuration")
errNoIssuerURL = errors.New("no IssuerURL provided for the OIDC configuration")
errInvalidAuthenticationHeaderFormat = errors.New("invalid authorization header format")
errFailedToObtainClaimsFromToken = errors.New("failed to get the subject from the token issued by the OIDC provider")
errClaimNotFound = errors.New("username claim from the OIDC configuration not found on the token returned by the OIDC provider")
errUsernameNotString = errors.New("the username returned by the OIDC provider isn't a regular string")
errGroupsClaimNotFound = errors.New("groups claim from the OIDC configuration not found on the token returned by the OIDC provider")
errNotAuthenticated = errors.New("authentication didn't succeed")
)

func newOIDCAuthenticator(cfg configauth.Authentication) (*oidcAuthenticator, error) {
if cfg.OIDC.Audience == "" {
return nil, errNoClientIDProvided
}
if cfg.OIDC.IssuerURL == "" {
return nil, errNoIssuerURL
}
if cfg.Attribute == "" {
cfg.Attribute = defaultAttribute
}

return &oidcAuthenticator{
attribute: cfg.Attribute,
config: *cfg.OIDC,
unaryInterceptor: defaultUnaryInterceptor,
streamInterceptor: defaultStreamInterceptor,
}, nil
}

func (o *oidcAuthenticator) Authenticate(ctx context.Context, headers map[string][]string) (context.Context, error) {
authHeaders := headers[o.attribute]
if len(authHeaders) == 0 {
return ctx, errNotAuthenticated
}

// we only use the first header, if multiple values exist
parts := strings.Split(authHeaders[0], " ")
if len(parts) != 2 {
return ctx, errInvalidAuthenticationHeaderFormat
}

idToken, err := o.verifier.Verify(ctx, parts[1])
if err != nil {
return ctx, fmt.Errorf("failed to verify token: %w", err)
}

claims := map[string]interface{}{}
if err = idToken.Claims(&claims); err != nil {
// currently, this isn't a valid condition, the Verify call a few lines above
// will already attempt to parse the payload as a json and set it as the claims
// for the token. As we are using a map to hold the claims, there's no way to fail
// to read the claims. It could fail if we were using a custom struct. Instead of
// swalling the error, it's better to make this future-proof, in case the underlying
// code changes
return ctx, errFailedToObtainClaimsFromToken
}

sub, err := getSubjectFromClaims(claims, o.config.UsernameClaim, idToken.Subject)
if err != nil {
return ctx, fmt.Errorf("failed to get subject from claims in the token: %w", err)
}
ctx = context.WithValue(ctx, subjectKey, sub)

gr, err := getGroupsFromClaims(claims, o.config.GroupsClaim)
if err != nil {
return ctx, fmt.Errorf("failed to get groups from claims in the token: %w", err)
}
ctx = context.WithValue(ctx, groupsKey, gr)

return ctx, nil
}

func (o *oidcAuthenticator) Start(context.Context) error {
provider, err := getProviderForConfig(o.config)
if err != nil {
return fmt.Errorf("failed to get configuration from the auth server: %w", err)
}
o.provider = provider

o.verifier = o.provider.Verifier(&oidc.Config{
ClientID: o.config.Audience,
})

return nil
}

func (o *oidcAuthenticator) Close() error {
// no-op at the moment
// once we implement caching of the tokens we might need this
return nil
}

func (o *oidcAuthenticator) UnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
return o.unaryInterceptor(ctx, req, info, handler, o.Authenticate)
}

func (o *oidcAuthenticator) StreamInterceptor(srv interface{}, str grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
return o.streamInterceptor(srv, str, info, handler, o.Authenticate)
}

func getSubjectFromClaims(claims map[string]interface{}, usernameClaim string, fallback string) (string, error) {
if len(usernameClaim) > 0 {
username, found := claims[usernameClaim]
if !found {
return "", errClaimNotFound
}

sUsername, ok := username.(string)
if !ok {
return "", errUsernameNotString
}

return sUsername, nil
}

return fallback, nil
}

func getGroupsFromClaims(claims map[string]interface{}, groupsClaim string) ([]string, error) {
if len(groupsClaim) > 0 {
var groups []string
rawGroup, ok := claims[groupsClaim]
if !ok {
return nil, errGroupsClaimNotFound
}
switch v := rawGroup.(type) {
case string:
groups = append(groups, v)
case []string:
groups = v
case []interface{}:
groups = make([]string, 0, len(v))
for i := range v {
groups = append(groups, fmt.Sprintf("%v", v[i]))
}
}

return groups, nil
}

return []string{}, nil
}

func getProviderForConfig(config configauth.OIDC) (*oidc.Provider, error) {
t := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 10 * time.Second,
DualStack: true,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 5 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}

cert, err := getIssuerCACertFromPath(config.IssuerCAPath)
if err != nil {
return nil, err // the errors from this path have enough context already
}

if cert != nil {
t.TLSClientConfig = &tls.Config{
RootCAs: x509.NewCertPool(),
}
t.TLSClientConfig.RootCAs.AddCert(cert)
}

client := &http.Client{
Timeout: 5 * time.Second,
Transport: t,
}
oidcContext := oidc.ClientContext(context.Background(), client)
return oidc.NewProvider(oidcContext, config.IssuerURL)
}

func getIssuerCACertFromPath(path string) (*x509.Certificate, error) {
if path == "" {
return nil, nil
}

rawCA, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("could not read the CA file %q: %w", path, err)
}

if len(rawCA) == 0 {
return nil, fmt.Errorf("could not read the CA file %q: empty file", path)
}

block, _ := pem.Decode(rawCA)
if block == nil {
return nil, fmt.Errorf("cannot decode the contents of the CA file %q: %w", path, err)
}

return x509.ParseCertificate(block.Bytes)
}
Loading

0 comments on commit 007f9f9

Please sign in to comment.