github.com/mirantis/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 }