github.com/equinix-metal/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/imagetranslation/translator.go (about)

     1  /*
     2  Copyright 2017 Mirantis
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package imagetranslation
    18  
    19  import (
    20  	"context"
    21  	"crypto"
    22  	"crypto/ecdsa"
    23  	"crypto/rsa"
    24  	"crypto/x509"
    25  	"encoding/pem"
    26  	"errors"
    27  	"regexp"
    28  	"strings"
    29  	"time"
    30  
    31  	"github.com/golang/glog"
    32  	"k8s.io/client-go/tools/clientcmd"
    33  
    34  	"github.com/Mirantis/virtlet/pkg/api/virtlet.k8s/v1"
    35  	"github.com/Mirantis/virtlet/pkg/image"
    36  )
    37  
    38  type imageNameTranslator struct {
    39  	allowRegexp  bool
    40  	translations map[string]*v1.ImageTranslation
    41  }
    42  
    43  // LoadConfigs implements ImageNameTranslator LoadConfigs
    44  func (t *imageNameTranslator) LoadConfigs(ctx context.Context, sources ...ConfigSource) {
    45  	translations := map[string]*v1.ImageTranslation{}
    46  	for _, source := range sources {
    47  		configs, err := source.Configs(ctx)
    48  		if err != nil {
    49  			glog.V(2).Infof("cannot get image translation configs from %s: %v", source.Description(), err)
    50  			continue
    51  		}
    52  		for _, cfg := range configs {
    53  			body, err := cfg.Payload()
    54  			if err != nil {
    55  				glog.V(2).Infof("cannot load image translation config %s from %s: %v", cfg.ConfigName(), source.Description(), err)
    56  				continue
    57  			}
    58  
    59  			translations[cfg.ConfigName()] = &body
    60  		}
    61  	}
    62  	t.translations = translations
    63  }
    64  
    65  func convertEndpoint(rule v1.TranslationRule, config *v1.ImageTranslation) image.Endpoint {
    66  	profile, exists := config.Transports[rule.Transport]
    67  	if !exists {
    68  		return image.Endpoint{
    69  			URL:          rule.URL,
    70  			MaxRedirects: -1,
    71  		}
    72  	}
    73  	if profile.TimeoutMilliseconds < 0 {
    74  		profile.TimeoutMilliseconds = 0
    75  	}
    76  	maxRedirects := -1
    77  	if profile.MaxRedirects != nil {
    78  		maxRedirects = *profile.MaxRedirects
    79  	}
    80  
    81  	var tlsConfig *image.TLSConfig
    82  	if profile.TLS != nil {
    83  		var certificates []image.TLSCertificate
    84  		for i, record := range profile.TLS.Certificates {
    85  			var x509Certs []*x509.Certificate
    86  			var privateKey crypto.PrivateKey
    87  
    88  			for _, data := range [2]string{record.Key, record.Cert} {
    89  				dataBytes := []byte(data)
    90  				for {
    91  					block, rest := pem.Decode(dataBytes)
    92  					if block == nil {
    93  						break
    94  					}
    95  					if block.Type == "CERTIFICATE" {
    96  						c, err := x509.ParseCertificate(block.Bytes)
    97  						if err != nil {
    98  							glog.V(2).Infof("error decoding certificate #%d from transport profile %s", i, rule.Transport)
    99  						} else {
   100  							x509Certs = append(x509Certs, c)
   101  						}
   102  					} else if privateKey == nil && strings.HasSuffix(block.Type, "PRIVATE KEY") {
   103  						k, err := parsePrivateKey(block.Bytes)
   104  						if err != nil {
   105  							glog.V(2).Infof("error decoding private key #%d from transport profile %s", i, rule.Transport)
   106  						} else {
   107  							privateKey = k
   108  						}
   109  					}
   110  					dataBytes = rest
   111  				}
   112  			}
   113  
   114  			for _, c := range x509Certs {
   115  				certificates = append(certificates, image.TLSCertificate{
   116  					Certificate: c,
   117  					PrivateKey:  privateKey,
   118  				})
   119  			}
   120  		}
   121  
   122  		tlsConfig = &image.TLSConfig{
   123  			ServerName:   profile.TLS.ServerName,
   124  			Insecure:     profile.TLS.Insecure,
   125  			Certificates: certificates,
   126  		}
   127  	}
   128  
   129  	return image.Endpoint{
   130  		URL:          rule.URL,
   131  		Timeout:      time.Millisecond * time.Duration(profile.TimeoutMilliseconds),
   132  		Proxy:        profile.Proxy,
   133  		ProfileName:  rule.Transport,
   134  		MaxRedirects: maxRedirects,
   135  		TLS:          tlsConfig,
   136  	}
   137  }
   138  
   139  func parsePrivateKey(der []byte) (crypto.PrivateKey, error) {
   140  	if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
   141  		return key, nil
   142  	}
   143  	if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
   144  		switch key := key.(type) {
   145  		case *rsa.PrivateKey, *ecdsa.PrivateKey:
   146  			return key, nil
   147  		default:
   148  			return nil, errors.New("tls: found unknown private key type in PKCS#8 wrapping")
   149  		}
   150  	}
   151  	if key, err := x509.ParseECPrivateKey(der); err == nil {
   152  		return key, nil
   153  	}
   154  
   155  	return nil, errors.New("tls: failed to parse private key")
   156  }
   157  
   158  // Translate implements ImageNameTranslator Translate
   159  func (t *imageNameTranslator) Translate(name string) image.Endpoint {
   160  	for _, translation := range t.translations {
   161  		prefix := translation.Prefix + "/"
   162  		unprefixedName := name
   163  		if prefix != "/" {
   164  			if !strings.HasPrefix(name, prefix) {
   165  				continue
   166  			}
   167  			unprefixedName = name[len(prefix):]
   168  		}
   169  		for _, r := range translation.Rules {
   170  			if r.Name != "" && r.Name == unprefixedName {
   171  				return convertEndpoint(r, translation)
   172  			}
   173  		}
   174  		if !t.allowRegexp {
   175  			continue
   176  		}
   177  		for _, r := range translation.Rules {
   178  			if r.Regex == "" {
   179  				continue
   180  			}
   181  			re, err := regexp.Compile(r.Regex)
   182  			if err != nil {
   183  				glog.V(2).Infof("invalid regexp in image translation config: %q", r.Regex)
   184  				continue
   185  			}
   186  			submatchIndexes := re.FindStringSubmatchIndex(unprefixedName)
   187  			if len(submatchIndexes) > 0 {
   188  				r.URL = string(re.ExpandString(nil, r.URL, unprefixedName, submatchIndexes))
   189  				return convertEndpoint(r, translation)
   190  			}
   191  		}
   192  	}
   193  	glog.V(1).Infof("Using URL %q without translation", name)
   194  	return image.Endpoint{URL: name, MaxRedirects: -1}
   195  }
   196  
   197  // NewImageNameTranslator creates an instance of ImageNameTranslator
   198  func NewImageNameTranslator(allowRegexp bool) ImageNameTranslator {
   199  	return &imageNameTranslator{
   200  		allowRegexp: allowRegexp,
   201  	}
   202  }
   203  
   204  // GetDefaultImageTranslator returns a default image translation that
   205  // uses CRDs and a config directory
   206  func GetDefaultImageTranslator(imageTranslationConfigsDir string, allowRegexp bool, clientCfg clientcmd.ClientConfig) image.Translator {
   207  	var sources []ConfigSource
   208  	if clientCfg != nil {
   209  		sources = append(sources, NewCRDSource("kube-system", clientCfg))
   210  	}
   211  	if imageTranslationConfigsDir != "" {
   212  		sources = append(sources, NewFileConfigSource(imageTranslationConfigsDir))
   213  	}
   214  	return func(ctx context.Context, name string) image.Endpoint {
   215  		translator := NewImageNameTranslator(allowRegexp)
   216  		translator.LoadConfigs(ctx, sources...)
   217  		return translator.Translate(name)
   218  	}
   219  }
   220  
   221  // GetEmptyImageTranslator returns an empty image translator that
   222  // doesn't apply any translations
   223  func GetEmptyImageTranslator() image.Translator {
   224  	return func(ctx context.Context, name string) image.Endpoint {
   225  		return NewImageNameTranslator(false).Translate(name)
   226  	}
   227  }