diff --git a/pkg/extdataservice/client.go b/pkg/extdataservice/client.go new file mode 100644 index 0000000..dffdd46 --- /dev/null +++ b/pkg/extdataservice/client.go @@ -0,0 +1,94 @@ +package extdataservice + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "fmt" + v1 "k8s.io/api/admission/v1" + "net" + "net/http" + "net/url" + "strings" +) + +const ( + Endpoint = "/" +) + +type ExtDataClient struct { + client *http.Client + url *url.URL +} + +func NewClient(apiUrl string, tlsConfig *tls.Config) (*ExtDataClient, error) { + parsedApiUrl, err := url.Parse(apiUrl) + if err != nil { + return nil, err + } + // Code path won't execute for localhost/sidecar call + if needsTLS(parsedApiUrl) && tlsConfig != nil { + return &ExtDataClient{client: &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConfig, + TLSNextProto: nil, + }}, url: parsedApiUrl}, nil + } + return &ExtDataClient{client: &http.Client{}, url: parsedApiUrl}, nil +} + +type ExtDataResponse struct { + Annotations map[string]string +} + +type ExtDataRequest struct { + AdmissionReview v1.AdmissionReview +} + +func (c *ExtDataClient) FetchExtData(request *ExtDataRequest) (*ExtDataResponse, error) { + requestBody, err := json.Marshal(request) + if err != nil { + return nil, fmt.Errorf("error encoding request payload: %v", err) + } + req, err := http.NewRequest("GET", c.url.String()+Endpoint, bytes.NewBuffer(requestBody)) + if err != nil { + return nil, fmt.Errorf("error creating request: %v", err) + } + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error sending request: %v", err.Error()) + } + + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("error failed with status code %v", resp.StatusCode) + } + var extAnnotationResponse ExtDataResponse + err = json.NewDecoder(resp.Body).Decode(&extAnnotationResponse) + if err != nil { + return nil, fmt.Errorf("error decoding response: %v", err) + } + return &extAnnotationResponse, nil +} + +func needsTLS(apiUrl *url.URL) bool { + if !isLocalhost(apiUrl) { + return true + } + return false +} + +func isLocalhost(u *url.URL) bool { + // Split the Host into host and port + host, _, err := net.SplitHostPort(u.Host) + if err != nil { + // If SplitHostPort fails, it might not have a port + host = u.Host + } + + // Convert host to lowercase for case-insensitive comparison + host = strings.ToLower(host) + + // Check if it's localhost or an IP loopback address + return host == "localhost" || host == "127.0.0.1" || host == "::1" +} diff --git a/pkg/injectionwebhook/config/config.go b/pkg/injectionwebhook/config/config.go index 69ecb1d..fe83af9 100644 --- a/pkg/injectionwebhook/config/config.go +++ b/pkg/injectionwebhook/config/config.go @@ -23,6 +23,7 @@ type WebhookConfig struct { CaFilePath string `long:"ca-file-path" required:"false" description:"file containing the CA cert"` SidecarConfigFile string `long:"sidecar-config-file" required:"true" description:"file containing the sidecar container configuration"` MutationConfigFile string `long:"mutation-config-file" required:"true" description:"file containing the mutation configuration"` + ExtDataUrl string `long:"ext-annotation-gen" required:"false" description:"url for fetching external data"` BuildInfoLabels string `long:"build-info-labels" required:"false" description:"additional build info metric labels"` // Flag to permit fallback to old, insecure TLS configurations. diff --git a/pkg/injectionwebhook/webhook.go b/pkg/injectionwebhook/webhook.go index 25689dc..99179cc 100644 --- a/pkg/injectionwebhook/webhook.go +++ b/pkg/injectionwebhook/webhook.go @@ -13,6 +13,7 @@ import ( "crypto/x509" "encoding/json" "fmt" + "github.com/salesforce/generic-sidecar-injector/pkg/extdataservice" "io/ioutil" "net/http" "strings" @@ -88,6 +89,7 @@ type WebhookServer struct { sidecarConfigTemplate *template.Template mutatingConfig *mutationconfig.MutationConfigs certificateReloader util.CertificateReloader + extDataClient *extdataservice.ExtDataClient } // NewWebhookServer is a constructor for webhookServer @@ -122,9 +124,30 @@ func (whsvr *WebhookServer) mutate(ar *v1.AdmissionReview) (admissionResponse *v glog.Errorf("api=mutate, message=new AdmissionReview, Kind=%v, Namespace=%v, Name=%v (%v), UID=%v, patchOperation=%v, UserInfo=%v", req.Kind, req.Namespace, req.Name, pod.Name, req.UID, req.Operation, req.UserInfo) + renderTemplateAnnotations := make(map[string]string) + for k, v := range pod.Annotations { + renderTemplateAnnotations[k] = v + } + if whsvr.extDataClient != nil { + extDataResponse, err := whsvr.extDataClient.FetchExtData(&extdataservice.ExtDataRequest{AdmissionReview: *ar}) + if err != nil { + glog.Errorf("api=mutate, reason=extAnnotationClient, message=error fetching ext annotations, err=%v", err) + return &v1.AdmissionResponse{ + Result: &metav1.Status{ + Message: err.Error(), + }, + }, nil + } + // Will override actual pod annotation if exists with same key + for k, v := range extDataResponse.Annotations { + renderTemplateAnnotations[k] = v + } + } + sidecarConfig, err := sidecarconfig.RenderTemplate(corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ - Annotations: pod.Annotations, + Labels: pod.Labels, + Annotations: renderTemplateAnnotations, }, Spec: corev1.PodSpec{ ServiceAccountName: pod.Spec.ServiceAccountName, @@ -313,6 +336,15 @@ func (whsvr *WebhookServer) Start() (chan bool, chan bool, error) { } whsvr.tlsServer.Handler = router + if whsvr.config.ExtDataUrl != "" { + extDataClient, err := extdataservice.NewClient(whsvr.config.ExtDataUrl, tlsConfig) + if err != nil { + glog.Errorf("api=Start, reason=extdataservice.NewClient, url=%v, err=%v", whsvr.config.ExtDataUrl, err) + return nil, nil, errors.Errorf("api=Start, reason=extdataservice.NewClient, url=%v, err=%v", whsvr.config.ExtDataUrl, err) + } + whsvr.extDataClient = extDataClient + } + // Channel to indicate when the server stopped listening for some reason doneListeningTLSChannel := make(chan bool) diff --git a/sidecarinjector b/sidecarinjector index 1d4d566..968c173 100755 Binary files a/sidecarinjector and b/sidecarinjector differ