go.mod: Update openshift-online/ocm-sdk-go

This requires golang-jwt/jwt/v4.
This commit is contained in:
Sanne Raymaekers 2022-05-17 19:15:17 +02:00
parent 56a7059b40
commit 7529382890
111 changed files with 4401 additions and 1462 deletions

View file

@ -22,7 +22,7 @@ import (
"context"
"fmt"
"github.com/golang-jwt/jwt"
"github.com/golang-jwt/jwt/v4"
)
// ContextWithToken creates a new context containing the given token.

View file

@ -35,8 +35,8 @@ import (
"sync"
"time"
"github.com/ghodss/yaml"
"github.com/golang-jwt/jwt"
"github.com/golang-jwt/jwt/v4"
"gopkg.in/yaml.v3"
"github.com/openshift-online/ocm-sdk-go/errors"
"github.com/openshift-online/ocm-sdk-go/logging"
@ -388,8 +388,8 @@ func (b *HandlerBuilder) Build() (handler *Handler, err error) {
// aclItem is the type used to read a single ACL item from a YAML document.
type aclItem struct {
Claim string `json:"claim"`
Pattern string `json:"pattern"`
Claim string `yaml:"claim"`
Pattern string `yaml:"pattern"`
}
// loadACLFile loads the given ACL file into the given map of ACL items.

View file

@ -22,7 +22,7 @@ import (
"fmt"
"time"
"github.com/golang-jwt/jwt"
"github.com/golang-jwt/jwt/v4"
)
// tokenRemaining determines if the given token will eventually expire (offile access tokens and

View file

@ -16,7 +16,9 @@ limitations under the License.
package authentication
import "github.com/golang-jwt/jwt"
import (
"github.com/golang-jwt/jwt/v4"
)
// tokenInfo stores information about a token. We need to store both the original text and the
// parsed objects because some tokens (refresh tokens in particular) may be opaque strings instead

View file

@ -22,23 +22,24 @@ package authentication
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"sync"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
//
"github.com/cenkalti/backoff/v4"
jwt "github.com/golang-jwt/jwt"
"github.com/golang-jwt/jwt/v4"
"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus"
"github.com/openshift-online/ocm-sdk-go/internal"
"github.com/openshift-online/ocm-sdk-go/logging"
"github.com/prometheus/client_golang/prometheus"
)
// Default values:
@ -80,20 +81,21 @@ type TransportWrapperBuilder struct {
// one that adds authorization tokens to requests.
type TransportWrapper struct {
// Fields used for basic functionality:
logger logging.Logger
clientID string
clientSecret string
user string
password string
scopes []string
agent string
clientSelector *internal.ClientSelector
tokenURL string
tokenServer *internal.ServerAddress
tokenMutex *sync.Mutex
tokenParser *jwt.Parser
accessToken *tokenInfo
refreshToken *tokenInfo
logger logging.Logger
clientID string
clientSecret string
user string
password string
scopes []string
agent string
clientSelector *internal.ClientSelector
tokenURL string
tokenServer *internal.ServerAddress
tokenMutex *sync.Mutex
tokenParser *jwt.Parser
accessToken *tokenInfo
refreshToken *tokenInfo
pullSecretAccessToken *tokenInfo
// Fields used for metrics:
metricsSubsystem string
@ -343,21 +345,43 @@ func (b *TransportWrapperBuilder) Build(ctx context.Context) (result *TransportW
// Parse the tokens:
var accessToken *tokenInfo
var refreshToken *tokenInfo
var pullSecretAccessToken *tokenInfo
for i, text := range b.tokens {
var object *jwt.Token
object, _, err = tokenParser.ParseUnverified(text, jwt.MapClaims{})
if err != nil {
b.logger.Debug(
ctx,
"Can't parse token %d, will assume that it is an opaque "+
"refresh token: %v",
"Can't parse token %d, will assume that it is either an "+
"opaque refresh token or pull secret access token: %v",
i, err,
)
refreshToken = &tokenInfo{
// Attempt to detect/parse the token as a pull-secret access token
err := parsePullSecretAccessToken(text)
if err != nil {
b.logger.Debug(
ctx,
"Can't parse pull secret access token %d, will assume "+
"that it is an opaque refresh token: %v",
i, err,
)
// Not a pull-secret access token, so assume a opaque refresh token
refreshToken = &tokenInfo{
text: text,
}
continue
}
// Parsing as a pull-secret access token was successful, treat it as such
pullSecretAccessToken = &tokenInfo{
text: text,
}
continue
}
claims, ok := object.Claims.(jwt.MapClaims)
if !ok {
err = fmt.Errorf("claims of token %d are of type '%T'", i, claims)
@ -525,24 +549,25 @@ func (b *TransportWrapperBuilder) Build(ctx context.Context) (result *TransportW
// Create and populate the object:
result = &TransportWrapper{
logger: b.logger,
clientID: clientID,
clientSecret: clientSecret,
user: b.user,
password: b.password,
scopes: scopes,
agent: b.agent,
clientSelector: clientSelector,
tokenURL: tokenURL,
tokenServer: tokenServer,
tokenMutex: &sync.Mutex{},
tokenParser: tokenParser,
accessToken: accessToken,
refreshToken: refreshToken,
metricsSubsystem: b.metricsSubsystem,
metricsRegisterer: b.metricsRegisterer,
tokenCountMetric: tokenCountMetric,
tokenDurationMetric: tokenDurationMetric,
logger: b.logger,
clientID: clientID,
clientSecret: clientSecret,
user: b.user,
password: b.password,
scopes: scopes,
agent: b.agent,
clientSelector: clientSelector,
tokenURL: tokenURL,
tokenServer: tokenServer,
tokenMutex: &sync.Mutex{},
tokenParser: tokenParser,
accessToken: accessToken,
refreshToken: refreshToken,
pullSecretAccessToken: pullSecretAccessToken,
metricsSubsystem: b.metricsSubsystem,
metricsRegisterer: b.metricsRegisterer,
tokenCountMetric: tokenCountMetric,
tokenDurationMetric: tokenDurationMetric,
}
return
@ -615,8 +640,16 @@ func (t *roundTripper) RoundTrip(request *http.Request) (response *http.Response
if request.Header == nil {
request.Header = make(http.Header)
}
// If the access token is a pull-secret-access-token type, a
// different Authorization header must be used
if token != "" {
request.Header.Set("Authorization", "Bearer "+token)
if err := parsePullSecretAccessToken(token); err == nil {
// It is a pull-secret access token
request.Header.Set("Authorization", "AccessToken "+token)
} else {
request.Header.Set("Authorization", "Bearer "+token)
}
}
// Call the wrapped transport:
@ -690,6 +723,12 @@ func (w *TransportWrapper) tokens(ctx context.Context, attempt int,
w.tokenMutex.Lock()
defer w.tokenMutex.Unlock()
// A pull-secret access token can just be used as-is
if w.pullSecretAccessToken != nil {
access = w.pullSecretAccessToken.text
return
}
// Check the expiration times of the tokens:
now := time.Now()
var accessExpires bool
@ -1042,6 +1081,25 @@ func (w *TransportWrapper) debugExpiry(ctx context.Context, typ string, token *t
}
}
// parsePullSecretAccessToken will parse the supplied token to verify conformity
// with that of a pull secret access token. A pull secret access token is of the
// form <cluster id>:<Base64d pull secret token>.
func parsePullSecretAccessToken(text string) error {
elems := strings.Split(text, ":")
if len(elems) != 2 {
return fmt.Errorf("Unparseable pull secret token")
}
_, err := uuid.Parse(elems[0])
if err != nil {
return fmt.Errorf("Unparseable pull secret token cluster ID")
}
_, err = base64.StdEncoding.DecodeString(elems[1])
if err != nil {
return fmt.Errorf("Unparseable pull secret token value")
}
return nil
}
// Names of fields in the token form:
const (
grantTypeField = "grant_type"

View file

@ -40,6 +40,7 @@ const ErrorNilKind = "ErrorNil"
// ErrorBuilder is a builder for the error type.
type ErrorBuilder struct {
bitmap_ uint32
status int
id string
href string
code string
@ -51,6 +52,7 @@ type ErrorBuilder struct {
// Error represents errors.
type Error struct {
bitmap_ uint32
status int
id string
href string
code string
@ -64,51 +66,76 @@ func NewError() *ErrorBuilder {
return &ErrorBuilder{}
}
// Status sets the HTTP status code.
func (b *ErrorBuilder) Status(value int) *ErrorBuilder {
b.status = value
b.bitmap_ |= 1
return b
}
// ID sets the identifier of the error.
func (b *ErrorBuilder) ID(value string) *ErrorBuilder {
b.id = value
b.bitmap_ |= 1
b.bitmap_ |= 2
return b
}
// HREF sets the link of the error.
func (b *ErrorBuilder) HREF(value string) *ErrorBuilder {
b.href = value
b.bitmap_ |= 2
b.bitmap_ |= 4
return b
}
// Code sets the code of the error.
func (b *ErrorBuilder) Code(value string) *ErrorBuilder {
b.code = value
b.bitmap_ |= 4
b.bitmap_ |= 8
return b
}
// Reason sets the reason of the error.
func (b *ErrorBuilder) Reason(value string) *ErrorBuilder {
b.reason = value
b.bitmap_ |= 8
b.bitmap_ |= 16
return b
}
// OperationID sets the identifier of the operation that caused the error.
func (b *ErrorBuilder) OperationID(value string) *ErrorBuilder {
b.operationID = value
b.bitmap_ |= 16
b.bitmap_ |= 32
return b
}
// Details sets additional details of the error.
func (b *ErrorBuilder) Details(value interface{}) *ErrorBuilder {
b.details = value
b.bitmap_ |= 32
b.bitmap_ |= 64
return b
}
// Copy copies the attributes of the given error into this
// builder, discarding any previous values.
func (b *ErrorBuilder) Copy(object *Error) *ErrorBuilder {
if object == nil {
return b
}
b.bitmap_ = object.bitmap_
b.status = object.status
b.id = object.id
b.href = object.href
b.code = object.code
b.reason = object.reason
b.details = object.details
b.operationID = object.operationID
return b
}
// Build uses the information stored in the builder to create a new error object.
func (b *ErrorBuilder) Build() (result *Error, err error) {
result = &Error{
status: b.status,
id: b.id,
href: b.href,
code: b.code,
@ -128,9 +155,27 @@ func (e *Error) Kind() string {
return ErrorKind
}
// Status returns the HTTP status code.
func (e *Error) Status() int {
if e != nil && e.bitmap_&1 != 0 {
return e.status
}
return 0
}
// GetStatus returns the HTTP status code of the error and a flag indicating
// if the status has a value.
func (e *Error) GetStatus() (value int, ok bool) {
ok = e != nil && e.bitmap_&1 != 0
if ok {
value = e.status
}
return
}
// ID returns the identifier of the error.
func (e *Error) ID() string {
if e != nil && e.bitmap_&1 != 0 {
if e != nil && e.bitmap_&2 != 0 {
return e.id
}
return ""
@ -139,7 +184,7 @@ func (e *Error) ID() string {
// GetID returns the identifier of the error and a flag indicating if the
// identifier has a value.
func (e *Error) GetID() (value string, ok bool) {
ok = e != nil && e.bitmap_&1 != 0
ok = e != nil && e.bitmap_&2 != 0
if ok {
value = e.id
}
@ -148,7 +193,7 @@ func (e *Error) GetID() (value string, ok bool) {
// HREF returns the link to the error.
func (e *Error) HREF() string {
if e != nil && e.bitmap_&2 != 0 {
if e != nil && e.bitmap_&4 != 0 {
return e.href
}
return ""
@ -157,7 +202,7 @@ func (e *Error) HREF() string {
// GetHREF returns the link of the error and a flag indicating if the
// link has a value.
func (e *Error) GetHREF() (value string, ok bool) {
ok = e != nil && e.bitmap_&2 != 0
ok = e != nil && e.bitmap_&4 != 0
if ok {
value = e.href
}
@ -166,7 +211,7 @@ func (e *Error) GetHREF() (value string, ok bool) {
// Code returns the code of the error.
func (e *Error) Code() string {
if e != nil && e.bitmap_&4 != 0 {
if e != nil && e.bitmap_&8 != 0 {
return e.code
}
return ""
@ -175,7 +220,7 @@ func (e *Error) Code() string {
// GetCode returns the link of the error and a flag indicating if the
// code has a value.
func (e *Error) GetCode() (value string, ok bool) {
ok = e != nil && e.bitmap_&4 != 0
ok = e != nil && e.bitmap_&8 != 0
if ok {
value = e.code
}
@ -184,7 +229,7 @@ func (e *Error) GetCode() (value string, ok bool) {
// Reason returns the reason of the error.
func (e *Error) Reason() string {
if e != nil && e.bitmap_&8 != 0 {
if e != nil && e.bitmap_&16 != 0 {
return e.reason
}
return ""
@ -193,7 +238,7 @@ func (e *Error) Reason() string {
// GetReason returns the link of the error and a flag indicating if the
// reason has a value.
func (e *Error) GetReason() (value string, ok bool) {
ok = e != nil && e.bitmap_&8 != 0
ok = e != nil && e.bitmap_&16 != 0
if ok {
value = e.reason
}
@ -202,7 +247,7 @@ func (e *Error) GetReason() (value string, ok bool) {
// OperationID returns the identifier of the operation that caused the error.
func (e *Error) OperationID() string {
if e != nil && e.bitmap_&16 != 0 {
if e != nil && e.bitmap_&32 != 0 {
return e.operationID
}
return ""
@ -211,7 +256,7 @@ func (e *Error) OperationID() string {
// GetOperationID returns the identifier of the operation that caused the error and
// a flag indicating if that identifier does have a value.
func (e *Error) GetOperationID() (value string, ok bool) {
ok = e != nil && e.bitmap_&16 != 0
ok = e != nil && e.bitmap_&32 != 0
if ok {
value = e.operationID
}
@ -220,7 +265,7 @@ func (e *Error) GetOperationID() (value string, ok bool) {
// Details returns the details of the error
func (e *Error) Details() interface{} {
if e != nil && e.bitmap_&32 != 0 {
if e != nil && e.bitmap_&64 != 0 {
return e.details
}
return nil
@ -229,7 +274,7 @@ func (e *Error) Details() interface{} {
// GetDetails returns the details of the error and a flag
// indicating if the details have a value.
func (e *Error) GetDetails() (value interface{}, ok bool) {
ok = e != nil && e.bitmap_&32 != 0
ok = e != nil && e.bitmap_&64 != 0
if ok {
value = e.details
}
@ -239,13 +284,16 @@ func (e *Error) GetDetails() (value interface{}, ok bool) {
// Error is the implementation of the error interface.
func (e *Error) Error() string {
chunks := make([]string, 0, 3)
if e.id != "" {
if e.bitmap_&1 != 0 {
chunks = append(chunks, fmt.Sprintf("status is %d", e.status))
}
if e.bitmap_&2 != 0 {
chunks = append(chunks, fmt.Sprintf("identifier is '%s'", e.id))
}
if e.code != "" {
if e.bitmap_&8 != 0 {
chunks = append(chunks, fmt.Sprintf("code is '%s'", e.code))
}
if e.operationID != "" {
if e.bitmap_&32 != 0 {
chunks = append(chunks, fmt.Sprintf("operation identifier is '%s'", e.operationID))
}
var result string
@ -255,7 +303,7 @@ func (e *Error) Error() string {
} else if size > 1 {
result = strings.Join(chunks[0:size-1], ", ") + " and " + chunks[size-1]
}
if e.reason != "" {
if e.bitmap_&16 != 0 {
if result != "" {
result = result + ": "
}
@ -283,6 +331,18 @@ func UnmarshalError(source interface{}) (object *Error, err error) {
err = iterator.Error
return
}
// UnmarshalErrorStatus reads an error from the given source and sets
// the given status code.
func UnmarshalErrorStatus(source interface{}, status int) (object *Error, err error) {
object, err = UnmarshalError(source)
if err != nil {
return
}
object.status = status
object.bitmap_ |= 1
return
}
func readError(iterator *jsoniter.Iterator) *Error {
object := &Error{}
for {
@ -291,24 +351,27 @@ func readError(iterator *jsoniter.Iterator) *Error {
break
}
switch field {
case "status":
object.status = iterator.ReadInt()
object.bitmap_ |= 1
case "id":
object.id = iterator.ReadString()
object.bitmap_ |= 1
object.bitmap_ |= 2
case "href":
object.href = iterator.ReadString()
object.bitmap_ |= 2
object.bitmap_ |= 4
case "code":
object.code = iterator.ReadString()
object.bitmap_ |= 4
object.bitmap_ |= 8
case "reason":
object.reason = iterator.ReadString()
object.bitmap_ |= 8
object.bitmap_ |= 16
case "operation_id":
object.operationID = iterator.ReadString()
object.bitmap_ |= 16
object.bitmap_ |= 32
case "details":
object.details = iterator.ReadAny().GetInterface()
object.bitmap_ |= 32
object.bitmap_ |= 64
default:
iterator.ReadAny()
}
@ -320,7 +383,10 @@ func readError(iterator *jsoniter.Iterator) *Error {
func MarshalError(e *Error, writer io.Writer) error {
stream := helpers.NewStream(writer)
writeError(e, stream)
stream.Flush()
err := stream.Flush()
if err != nil {
return err
}
return stream.Error
}
func writeError(e *Error, stream *jsoniter.Stream) {
@ -328,31 +394,36 @@ func writeError(e *Error, stream *jsoniter.Stream) {
stream.WriteObjectField("kind")
stream.WriteString(ErrorKind)
if e.bitmap_&1 != 0 {
stream.WriteMore()
stream.WriteObjectField("status")
stream.WriteInt(e.status)
}
if e.bitmap_&2 != 0 {
stream.WriteMore()
stream.WriteObjectField("id")
stream.WriteString(e.id)
}
if e.bitmap_&2 != 0 {
if e.bitmap_&4 != 0 {
stream.WriteMore()
stream.WriteObjectField("href")
stream.WriteString(e.href)
}
if e.bitmap_&4 != 0 {
if e.bitmap_&8 != 0 {
stream.WriteMore()
stream.WriteObjectField("code")
stream.WriteString(e.code)
}
if e.bitmap_&8 != 0 {
if e.bitmap_&16 != 0 {
stream.WriteMore()
stream.WriteObjectField("reason")
stream.WriteString(e.reason)
}
if e.bitmap_&16 != 0 {
if e.bitmap_&32 != 0 {
stream.WriteMore()
stream.WriteObjectField("operation_id")
stream.WriteString(e.operationID)
}
if e.bitmap_&32 != 0 {
if e.bitmap_&64 != 0 {
stream.WriteMore()
stream.WriteObjectField("details")
stream.WriteVal(e.details)

View file

@ -34,7 +34,14 @@ func AddValue(query *url.Values, name string, value interface{}) {
if *query == nil {
*query = make(url.Values)
}
query.Add(name, fmt.Sprintf("%v", value))
var text string
switch typed := value.(type) {
case time.Time:
text = typed.UTC().Format(time.RFC3339)
default:
text = fmt.Sprintf("%v", value)
}
query.Add(name, text)
}
// CopyQuery creates a copy of the given set of query parameters.
@ -67,6 +74,12 @@ func CopyHeader(header http.Header) http.Header {
return result
}
const impersonateUserHeader = "Impersonate-User"
func AddImpersonationHeader(header *http.Header, user string) {
AddHeader(header, impersonateUserHeader, user)
}
// CopyValues copies a slice of strings.
func CopyValues(values []string) []string {
if values == nil {
@ -113,6 +126,7 @@ func PollContext(
// Create a cancellable context so that we can explicitly cancel it when we know that the next
// iteration of the loop will be after the deadline:
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// If no expected status has been explicitly specified then add the default:
if len(statuses) == 0 {

View file

@ -349,23 +349,12 @@ func (s *ClientSelector) createTransport(ctx context.Context,
dialer := net.Dialer{}
return dialer.DialContext(ctx, UnixNetwork, address.Socket)
}
transport.DialTLS = func(_, _ string) (net.Conn, error) { // nolint
// TODO: This ignores the passed context because it isn't currently
// supported. Once we migrate to Go 1.15 it should be done like
// this:
//
// transport.DialTLSContext = func(ctx context.Context,
// _, _ string) (net.Conn, error) {
// dialer := tls.Dialer{
// Config: config,
// }
// return dialer.DialContext(ctx, network, address.Socket)
// }
//
// This will only have a negative impact in applications that
// specify a deadline or timeout in the passed context, as it
// will be ignored.
return tls.Dial(UnixNetwork, address.Socket, config)
transport.DialTLSContext = func(ctx context.Context, _, _ string) (net.Conn,
error) {
dialer := tls.Dialer{
Config: config,
}
return dialer.DialContext(ctx, UnixNetwork, address.Socket)
}
}

View file

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
/*

View file

@ -1,3 +1,4 @@
//go:build windows
// +build windows
/*
@ -34,18 +35,65 @@ import (
// can do (or know to do) till Go learns to read the Windows CA trust store.
func loadSystemCAs() (pool *x509.CertPool, err error) {
pool = x509.NewCertPool()
pool.AppendCertsFromPEM(ssoCA)
pool.AppendCertsFromPEM(ssoCA1)
pool.AppendCertsFromPEM(ssoCA2)
pool.AppendCertsFromPEM(apiCA1)
pool.AppendCertsFromPEM(apiCA2)
return
}
// This certificate has been obtained with the following command:
// The SSO certificates has been obtained with the following command:
//
// $ openssl s_client \
// -connect sso.redhat.com:443 \
// -showcerts
var ssoCA = []byte(`
// $ openssl s_client -connect sso.redhat.com:443 -showcerts
var ssoCA1 = []byte(`
-----BEGIN CERTIFICATE-----
MIIH5jCCBs6gAwIBAgIQCXX/who4sAsoWCcqTgTIwDANBgkqhkiG9w0BAQsFADB1
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk
IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTIxMTIwMTAwMDAwMFoXDTIyMTIwMTIz
NTk1OVowgcoxHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB
BAGCNzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQF
EwcyOTQ1NDM2MQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmEx
EDAOBgNVBAcTB1JhbGVpZ2gxFjAUBgNVBAoTDVJlZCBIYXQsIEluYy4xFzAVBgNV
BAMTDnNzby5yZWRoYXQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAuJ3OufFJ2mm6s5DcS8QQ2HukHUVwVbooK8nT5VFm2xl1TcL18iEhUr+nBlSg
tG6JvLch7GwZ9vO64sxbDLOck7cVHU5ddu59Wv/KwKiwe9Hs6D7bCxQhUINWrFEN
3nPaBPLMOdPBawpbtO9Uh+2+aPG4gQASztb40wa0eEnwhe4QjEWhGBx5eshxg8xX
CokWLucwR19GQxkDCRYlSHZ8FM3h8GVKULrUAiiwANkIXZ5ffKDCr//xxIy6Qt5F
zGc3SExQxM5aRnIi24hzasyIYgK0wUBl9bfvMSHQRpe/oDQ7pVG+DM/DMsgqbPpa
363b9liTwcWaWNIfNOPiuUz0eQIDAQABo4IEGjCCBBYwHwYDVR0jBBgwFoAUPdNQ
pdagre7zSmAKZdMh1Pj41g8wHQYDVR0OBBYEFOcUAXLthwGqFZOEpHQzU3vqeYs2
MIHEBgNVHREEgbwwgbmCFHNzby5zdGFnZS5yZWRoYXQuY29tgg5zc28ucmVkaGF0
LmNvbYIVb2NzcC5zdGFnZS5yZWRoYXQuY29tgg9vY3NwLnJlZGhhdC5jb22CF29j
c3AucHJlcHJvZC5yZWRoYXQuY29tghNvY3NwLmRldi5yZWRoYXQuY29tghVhdXRo
LnN0YWdlLnJlZGhhdC5jb22CD2F1dGgucmVkaGF0LmNvbYITYXV0aC5kZXYucmVk
aGF0LmNvbTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG
AQUFBwMCMHUGA1UdHwRuMGwwNKAyoDCGLmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv
bS9zaGEyLWV2LXNlcnZlci1nMy5jcmwwNKAyoDCGLmh0dHA6Ly9jcmw0LmRpZ2lj
ZXJ0LmNvbS9zaGEyLWV2LXNlcnZlci1nMy5jcmwwSgYDVR0gBEMwQTALBglghkgB
hv1sAgEwMgYFZ4EMAQEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2Vy
dC5jb20vQ1BTMIGIBggrBgEFBQcBAQR8MHowJAYIKwYBBQUHMAGGGGh0dHA6Ly9v
Y3NwLmRpZ2ljZXJ0LmNvbTBSBggrBgEFBQcwAoZGaHR0cDovL2NhY2VydHMuZGln
aWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkV4dGVuZGVkVmFsaWRhdGlvblNlcnZlckNB
LmNydDAMBgNVHRMBAf8EAjAAMIIBgAYKKwYBBAHWeQIEAgSCAXAEggFsAWoAdwAp
eb7wnjk5IfBWc59jpXflvld9nGAK+PlNXSZcJV3HhAAAAX126giiAAAEAwBIMEYC
IQCpy8SAGEcmYEIHk8WJeJGX2FrLXf2xgvMfQwI+tCVFagIhALaVEmIsytgKw36M
PZuMdMutuBxIYod3QDCJPm/Rp7W6AHcAUaOw9f0BeZxWbbg3eI8MpHrMGyfL956I
QpoN/tSLBeUAAAF9duoIdgAABAMASDBGAiEAzFInu58+b4YTNxVBxeMjADGM8VEc
GYZeVOV1yTVvI4ICIQDoW/o+hz0chsuGtxiHTl7+gDv3XBItNNmOJHS6pxwNwAB2
AEHIyrHfIkZKEMahOglCh15OMYsbA+vrS8do8JBilgb2AAABfXbqCPsAAAQDAEcw
RQIhAIc96iDui/2j/56OXDMERH33bC1LoehOhUN1anvZhT6QAiAge864T+lTU9vK
kZ3pp30K95Jtr+qsaSBQ8PFQjE38UDANBgkqhkiG9w0BAQsFAAOCAQEAwK1NVoNb
GT9W5AWkjeu2rbJUeo+06lgVJtCV4sS8dIkloKfFFn/aZ1tBmiia+i6do5Zej+pZ
vf33jr3OxCWEDKoNFA3kjLx6JwSpYovcaVccTE7HnNrS05wwEN8pTcoDIVYBgnEh
FLlMJ8X4c1ba+1ZE08ZzoJAKNLFcl3Hz7Mt/Umg/oSbNZcJINWvpERwUDKqscPIQ
ZVSFgKGfs4Rdcv+ZXZBgTnN5eNWA6ryySsQuoJDhsHBYcXKLGKu8qemwYfaGVMzo
ER7HUMzQI3HxQyX5t+feEaU6Vjjrq9/pFwZyaYlji205jEHWqaGEgJpNlIVY+/PT
/5lvjY2E7c4fmw==
-----END CERTIFICATE-----
`)
var ssoCA2 = []byte(`
-----BEGIN CERTIFICATE-----
MIIEtjCCA56gAwIBAgIQDHmpRLCMEZUgkmFf4msdgzANBgkqhkiG9w0BAQsFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
@ -76,74 +124,67 @@ oVWNWlZopCJwqjyBcdmdqEU79OX2olHdx3ti6G8MdOu42vi/hw15UJGQmxg7kVkn
-----END CERTIFICATE-----
`)
// This certificate has been obtained with the following command:
// The API certificates have been obtained with the following command:
//
// $ openssl s_client \
// -connect api.openshift.com:443 \
// -showcerts
// $ openssl s_client -connect api.openshift.com:443 -showcerts
var apiCA1 = []byte(`
-----BEGIN CERTIFICATE-----
MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw
WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP
R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx
sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm
NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg
Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG
/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC
AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA
FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw
AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw
Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB
gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W
PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl
ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz
CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm
lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4
avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2
yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O
yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids
hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+
HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv
MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
nLRbwHOoq7hHwg==
MIIFmzCCBSGgAwIBAgIQBQ+NDVzn4QRZqly06Ep3yjAKBggqhkjOPQQDAzBWMQsw
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMTAwLgYDVQQDEydEaWdp
Q2VydCBUTFMgSHlicmlkIEVDQyBTSEEzODQgMjAyMCBDQTEwHhcNMjIwNDI5MDAw
MDAwWhcNMjMwNTAzMjM1OTU5WjBsMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9y
dGggQ2Fyb2xpbmExEDAOBgNVBAcTB1JhbGVpZ2gxFjAUBgNVBAoTDVJlZCBIYXQs
IEluYy4xGjAYBgNVBAMTEWFwaS5vcGVuc2hpZnQuY29tMHYwEAYHKoZIzj0CAQYF
K4EEACIDYgAEGdA2EA1g7ynRHLSKRjOlyPtqPnFoEAadzuNqedwTWBN3bA16Q6tr
j18rsF6kERbHmypiNMHDwymxQuYhEEJdlUL9zWnDl//Tt3P97WlJ0yQ96i478ofG
E73IYHzK8C8No4IDnDCCA5gwHwYDVR0jBBgwFoAUCrwIKReMpTlteg7OM8cus+37
w3owHQYDVR0OBBYEFJC5XsZwzzRxFn5ghyFFxkdygoynMDMGA1UdEQQsMCqCEWFw
aS5vcGVuc2hpZnQuY29tghV3d3cuYXBpLm9wZW5zaGlmdC5jb20wDgYDVR0PAQH/
BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjCBmwYDVR0fBIGT
MIGQMEagRKBChkBodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUTFNI
eWJyaWRFQ0NTSEEzODQyMDIwQ0ExLTEuY3JsMEagRKBChkBodHRwOi8vY3JsNC5k
aWdpY2VydC5jb20vRGlnaUNlcnRUTFNIeWJyaWRFQ0NTSEEzODQyMDIwQ0ExLTEu
Y3JsMD4GA1UdIAQ3MDUwMwYGZ4EMAQICMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93
d3cuZGlnaWNlcnQuY29tL0NQUzCBhQYIKwYBBQUHAQEEeTB3MCQGCCsGAQUFBzAB
hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wTwYIKwYBBQUHMAKGQ2h0dHA6Ly9j
YWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRMU0h5YnJpZEVDQ1NIQTM4NDIw
MjBDQTEtMS5jcnQwCQYDVR0TBAIwADCCAX8GCisGAQQB1nkCBAIEggFvBIIBawFp
AHcArfe++nz/EMiLnT2cHj4YarRnKV3PsQwkyoWGNOvcgooAAAGAc3NizAAABAMA
SDBGAiEAwR6POASNS3R+vdTvqP0LpoP0VB8m6JV3P8xn//Z10fYCIQDyV7C7V7yf
XuXzorhEXWg2npekZVkT0fS7jTUSmZJgTgB2ADXPGRu/sWxXvw+tTG1Cy7u2JyAm
Ueo/4SrvqAPDO9ZMAAABgHNzYsoAAAQDAEcwRQIgcTIfCrJMjzjuX3qnyR4lWwI9
e7AIjAm7P+c9B7CpgoECIQDUhSzYogCMx5QdFqFEi18KlX89bjKscSukieVbzc98
DwB2ALNzdwfhhFD4Y4bWBancEQlKeS2xZwwLh9zwAw55NqWaAAABgHNzYvwAAAQD
AEcwRQIhAN3WIvRA3tejaDj1ceCPQgb4tK97AOqWbdzOa4CbA6+oAiAbv0wFXElw
vuMPbYUjraXeyntGPfmTf/8MNlkd0aW20DAKBggqhkjOPQQDAwNoADBlAjEAiho0
712CjvYGMezC6cF6V8+lPWjb1PGRXzTMNYNBvUp0jCla5oznm6hLRpcqw8z3AjAq
sjE4cTA4Jy91GD6dAwd9uhwvEl5IpHIMb8GkxOybDAAHsioc3AGlfXFuls9GBdc=
-----END CERTIFICATE-----
`)
var apiCA2 = []byte(`
-----BEGIN CERTIFICATE-----
MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC
ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL
wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D
LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK
4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5
bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y
sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ
Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4
FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc
SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql
PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND
TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1
c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx
+tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB
ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu
b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E
U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu
MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC
5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW
9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG
WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O
he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC
Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5
MIIEFzCCAv+gAwIBAgIQB/LzXIeod6967+lHmTUlvTANBgkqhkiG9w0BAQwFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaMFYxCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMDAuBgNVBAMTJ0RpZ2lDZXJ0IFRMUyBI
eWJyaWQgRUNDIFNIQTM4NCAyMDIwIENBMTB2MBAGByqGSM49AgEGBSuBBAAiA2IA
BMEbxppbmNmkKaDp1AS12+umsmxVwP/tmMZJLwYnUcu/cMEFesOxnYeJuq20ExfJ
qLSDyLiQ0cx0NTY8g3KwtdD3ImnI8YDEe0CPz2iHJlw5ifFNkU3aiYvkA8ND5b8v
c6OCAYIwggF+MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFAq8CCkXjKU5
bXoOzjPHLrPt+8N6MB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA4G
A1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYI
KwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j
b20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp
Q2VydEdsb2JhbFJvb3RDQS5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2Ny
bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDA9BgNVHSAE
NjA0MAsGCWCGSAGG/WwCATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgG
BmeBDAECAzANBgkqhkiG9w0BAQwFAAOCAQEAR1mBf9QbH7Bx9phdGLqYR5iwfnYr
6v8ai6wms0KNMeZK6BnQ79oU59cUkqGS8qcuLa/7Hfb7U7CKP/zYFgrpsC62pQsY
kDUmotr2qLcy/JUjS8ZFucTP5Hzu5sn4kL1y45nDHQsFfGqXbbKrAjbYwrwsAZI/
BKOLdRHHuSm8EdCGupK8JvllyDfNJvaGEwwEqonleLHBTnm8dqMLUeTF0J5q/hos
Vq4GNiejcxwIfZMy0MJEGdqN9A57HSgDKwmKdsp33Id6rHtSJlWncg+d0ohP/rEh
xRqhqjn1VtvChMQ1H3Dau0bwhr9kAMQ+959GG50jBbl9s08PqUU643QwmA==
-----END CERTIFICATE-----
`)