From 3f2e76c75a1919bcc0fd8d3f7c776c6bcb48ac3c Mon Sep 17 00:00:00 2001 From: "Jian.Li" <74582607+leejanee@users.noreply.github.com> Date: Wed, 8 Dec 2021 11:39:04 +0800 Subject: [PATCH] Feat: HTTP Provider requires adding certificate chain from secret (#2771) * http support load tls certs * fix: TLS MinVersion too low --- pkg/builtin/http/http.go | 49 +++++++++++- pkg/builtin/http/http_test.go | 64 +++++++++++++++ pkg/builtin/http/testdata/certs.go | 39 +++++++++ .../v1alpha2/application/generator.go | 3 + pkg/stdlib/pkgs/http.cue | 1 + pkg/workflow/providers/http/do.go | 64 ++++++++++++++- pkg/workflow/providers/http/do_test.go | 79 ++++++++++++++++++- pkg/workflow/tasks/discover.go | 3 +- 8 files changed, 295 insertions(+), 7 deletions(-) create mode 100644 pkg/builtin/http/testdata/certs.go diff --git a/pkg/builtin/http/http.go b/pkg/builtin/http/http.go index 9b121b5ea..5f62d13a5 100644 --- a/pkg/builtin/http/http.go +++ b/pkg/builtin/http/http.go @@ -19,10 +19,13 @@ package http import ( "context" "crypto/tls" + "crypto/x509" "io" "net/http" + "time" "cuelang.org/go/cue" + "github.com/pkg/errors" "github.com/oam-dev/kubevela/pkg/builtin/registry" ) @@ -54,7 +57,13 @@ func (c *HTTPCmd) Run(meta *registry.Meta) (res interface{}, err error) { method = meta.String("method") u = meta.String("url") ) - var r io.Reader + var ( + r io.Reader + client = &http.Client{ + Transport: &http.Transport{}, + Timeout: time.Second * 3, + } + ) if obj := meta.Obj.Lookup("request"); obj.Exists() { if v := obj.Lookup("body"); v.Exists() { r, err = v.Reader() @@ -83,7 +92,43 @@ func (c *HTTPCmd) Run(meta *registry.Meta) (res interface{}, err error) { } req.Header = header req.Trailer = trailer - resp, err := c.Client.Do(req) + + if tlsConfig := meta.Obj.Lookup("tls_config"); tlsConfig.Exists() { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + NextProtos: []string{"http/1.1"}, + }, + } + ca := tlsConfig.Lookup("ca") + if caCrt, err := ca.String(); err != nil { + return nil, errors.WithMessage(err, "parse ca") + } else { + pool := x509.NewCertPool() + pool.AppendCertsFromPEM([]byte(caCrt)) + tr.TLSClientConfig.RootCAs = pool + } + + cert := tlsConfig.Lookup("client_crt") + key := tlsConfig.Lookup("client_key") + if cert.Exists() && key.Exists() { + crtData, err := cert.String() + if err != nil { + return nil, err + } + keyData, err := key.String() + if err != nil { + return nil, err + } + cliCrt, err := tls.X509KeyPair([]byte(crtData), []byte(keyData)) + if err != nil { + return nil, errors.WithMessage(err, "parse client keypair") + } + tr.TLSClientConfig.Certificates = []tls.Certificate{cliCrt} + } + + client.Transport = tr + } + resp, err := client.Do(req) if err != nil { return nil, err } diff --git a/pkg/builtin/http/http_test.go b/pkg/builtin/http/http_test.go index 652aa506a..dee883ba4 100644 --- a/pkg/builtin/http/http_test.go +++ b/pkg/builtin/http/http_test.go @@ -17,6 +17,9 @@ limitations under the License. package http import ( + "crypto/tls" + "crypto/x509" + "encoding/base64" "encoding/json" "fmt" "net" @@ -27,6 +30,7 @@ import ( "cuelang.org/go/cue" "github.com/bmizerany/assert" + "github.com/oam-dev/kubevela/pkg/builtin/http/testdata" "github.com/oam-dev/kubevela/pkg/builtin/registry" ) @@ -96,6 +100,29 @@ func TestHTTPCmdRun(t *testing.T) { } +func TestHTTPSRun(t *testing.T) { + s := newMockHttpsServer() + defer s.Close() + r := cue.Runtime{} + reqInst, err := r.Compile("-", `method: "GET" +url: "https://127.0.0.1:8443/api/v1/token?val=test-token"`) + if err != nil { + t.Fatal(err) + } + reqInst, _ = reqInst.Fill(decodeCert(testdata.MockCerts.Ca), "tls_config", "ca") + reqInst, _ = reqInst.Fill(decodeCert(testdata.MockCerts.ClientCrt), "tls_config", "client_crt") + reqInst, _ = reqInst.Fill(decodeCert(testdata.MockCerts.ClientKey), "tls_config", "client_key") + + runner, _ := newHTTPCmd(cue.Value{}) + got, err := runner.Run(®istry.Meta{Obj: reqInst.Value()}) + if err != nil { + t.Error(err) + } + body := (got.(map[string]interface{}))["body"].(string) + + assert.Equal(t, "{\"token\":\"test-token\"}", body) +} + // NewMock mock the http server func NewMock() *httptest.Server { ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -118,3 +145,40 @@ func NewMock() *httptest.Server { ts.Start() return ts } + +func newMockHttpsServer() *httptest.Server { + ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + fmt.Printf("Expected 'GET' request, got '%s'", r.Method) + } + if r.URL.EscapedPath() != "/api/v1/token" { + fmt.Printf("Expected request to '/person', got '%s'", r.URL.EscapedPath()) + } + r.ParseForm() + token := r.Form.Get("val") + tokenBytes, _ := json.Marshal(map[string]interface{}{"token": token}) + + w.WriteHeader(http.StatusOK) + w.Write(tokenBytes) + })) + l, _ := net.Listen("tcp", "127.0.0.1:8443") + ts.Listener.Close() + ts.Listener = l + + pool := x509.NewCertPool() + pool.AppendCertsFromPEM([]byte(decodeCert(testdata.MockCerts.Ca))) + cert, _ := tls.X509KeyPair([]byte(decodeCert(testdata.MockCerts.ServerCrt)), []byte(decodeCert(testdata.MockCerts.ServerKey))) + ts.TLS = &tls.Config{ + ClientCAs: pool, + ClientAuth: tls.RequireAndVerifyClientCert, + Certificates: []tls.Certificate{cert}, + NextProtos: []string{"http/1.1"}, + } + ts.StartTLS() + return ts +} + +func decodeCert(in string) string { + out, _ := base64.StdEncoding.DecodeString(in) + return string(out) +} diff --git a/pkg/builtin/http/testdata/certs.go b/pkg/builtin/http/testdata/certs.go new file mode 100644 index 000000000..68a7d9499 --- /dev/null +++ b/pkg/builtin/http/testdata/certs.go @@ -0,0 +1,39 @@ +/* +Copyright 2021 The KubeVela 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 testdata + +var ( + _ = `LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBcHRMN21JQmdaaDVEd1FYYkJXaHRSeURGK01qaytQeStNU1Vyck5pc1EyeXF4cnphCjhEeWw5a0pRWW5oMnVYVFV1RnBzbDRhM1J5dEJaVkdrMDNQT0RXOWJIblR2QUNPZHJjMnR2WmR5ZXRXU1ZtQ2cKOHhuc3Y3WHVQS0VGb0VwakVaMDdCWjY2blFIRDg2MHFMeGFGRWtMNHk2MzU5SThWVlRBYk5RejVPQ3dmM29mUQpMN0JPL2RVNUJtRTNXTDhhVHF3SXRSa0hJeE5pWCs4OWU2Z3dCY3RHdUZLR3ZacFhGaW1VeXA1Y0crVWI2RzkyCi9KUTZJWm45dGFIZ3NFYWIvWUNwZ2U1Rkp5WVR1dzVlakhRajRYNVh3ZVRKU0tsN0UwUmZhMjl5VnM5aXdhNDQKcmVNSzVXR2hVUFl6T1o0MURGZnU1MmJnMjVPODF6QWJFSFpLUndJREFRQUJBb0lCQUNUVUJ2OFB1RGhURGhvYQp0Tk5vemxjWmdSci9IcTFvL29QUzlPVmZvQWZ5Z1hFR1dEOFk1SHFOQVRuNzVobmpGT0x0ODNNd0psM3J5ckFYCmFnL1VUUFRpVkhkUTBVSnltbWk0TTFiYmpFWlp4OGlSNUhaR2p1Rnp4SGhXQSt2ekFCUHZaZ3hEa21iKzhNZG0KdngxT0YycUVwbkF3cERHOU5MUnR2bFBqM1ZEczhVODU2c2hWeDdBdFE3RGJUWkQwdEpsQ0pzTzR5TitjL1oxOApiRzJKNDB2RWFLalVGTE9HNitScE43NEZLeGtvOFJJejZxeERQMk5VMUg1ajVVVi9tZXdRdDBsRTNqbEc5MmcvCnVwTngyK0xnYUkrMWhCR3AzV2prQlRWcWloZWxrUk5XZkNLczdXOHJtYk83V3MvK2cwcVNidnAvUjBWQWpQd0MKdGt4SENFRUNnWUVBM2s3K0hOVkNZY0YxN2k2ZTJnNTJDVHV0cDJBYzkvSUNMdVJzNGFRZlB4MkxFc2VDalJnNgovaHNsOGpLbmRDS1JQdTBJbkoxckF4NzVrZXBKZWpWcTBIbkFEN2VtcVhuMDN0UjJmb3hvbkxBOEtQMzdSSnJqClhlZ0k5NiswWUU3QUY5dWZqQVhPeXpFU3RQVkNSVDlJOFRMSlEwRFhraW56bDhVUm5aZ1RjdmtDZ1lFQXdCdFYKLzNnbFR5Z0syNTFpMS9FakdrK3I3THF5NzdCY29LVzZHTm91K0FiQ3gxalhZVE1URDNTRXVyMzBueHB6VWNkdgpIbEI1NkI2Q1JmRkdXN0o1U0tkeXI5WmhQUUtITUQ1TkZhbm00S1F4NmZmVFhubExRdnhhT2c2TFRnTDRSdjFyCjVaeUdEbDhBKzRRckpNVk1OOTZOVEY1VDB0TXRUaHlIVnpLbHR6OENnWUJ3Q3BQYjZFZUtpVHhzakthVzg4N2QKbkd4Sy9RL2NqdVkyeC8xd1E0MVQvQW5KcnkvRytMMVNzRkFSbnlIeVVER3Y2enI1NUFTNUQvVnNhdzRaUDY3VAozMmpEQXlaR0tDY1gzekRSV3VhbWdkUHdQUUZVZEZPL1VtQ2lwTFZlREpLWDg2S1hxWjJ0bnMvMHo5OVVreTZxCkVaU0tCclllL25HOHZoL0FzNUtwMFFLQmdRQzFxT1BncWFkMk8rSlFuSHE4d3UwejAwVTduYXpabFlkeDdtV1YKWExUdm04MFNuME5FU2Z6ckwzN1g3QXJuYlNiQm5YckpTc2FNcGxVQWVORFVvMmVuT1pqdENDZDVmdXVCeGxnMApkUzY3SE9tS1d1ekl1S0JmM3F3Zm5HTkV5UEFvaVRvL3JZempDQm13dmVIaWFxUFJiU1Ztb3doWEk1VUMrVjFPCktybWtGd0tCZ1FEVERDWlg1WWQ5ZUdXZG1OM3pUU2Z6YkRrRkxqZkYyYTVBK2lDL281TmoyVmpHRG4xTjRvVUwKajF0dVZLb0xoVjhVZzd0Lzc4V0V0UkRnK1p3QVZhSW84bE1zU244dDVQNFFrY2pkSDI4bHpFaTQwWHpxQkF0Lwpoalppb1pNN2ZHUmJWK29yakZSQ2tZWnNaMUdua2FrbG5Mdk4vYVRuM25HV2tEZjFaZGM0YVE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQ==` //ca.key + caCrt = `LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNyakNDQVpZQ0NRRDV0K3E3ZkswSlVEQU5CZ2txaGtpRzl3MEJBUXNGQURBWk1SY3dGUVlEVlFRRERBNHEKTG10bGRXSmxkbVZzWVM1cGJ6QWVGdzB5TVRFeE1qSXdPRFEyTlRsYUZ3MHpOVEE0TURFd09EUTJOVGxhTUJreApGekFWQmdOVkJBTU1EaW91YTJWMVltVjJaV3hoTG1sdk1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQXB0TDdtSUJnWmg1RHdRWGJCV2h0UnlERitNamsrUHkrTVNVcnJOaXNRMnlxeHJ6YThEeWwKOWtKUVluaDJ1WFRVdUZwc2w0YTNSeXRCWlZHazAzUE9EVzliSG5UdkFDT2RyYzJ0dlpkeWV0V1NWbUNnOHhucwp2N1h1UEtFRm9FcGpFWjA3Qlo2Nm5RSEQ4NjBxTHhhRkVrTDR5NjM1OUk4VlZUQWJOUXo1T0N3ZjNvZlFMN0JPCi9kVTVCbUUzV0w4YVRxd0l0UmtISXhOaVgrODllNmd3QmN0R3VGS0d2WnBYRmltVXlwNWNHK1ViNkc5Mi9KUTYKSVpuOXRhSGdzRWFiL1lDcGdlNUZKeVlUdXc1ZWpIUWo0WDVYd2VUSlNLbDdFMFJmYTI5eVZzOWl3YTQ0cmVNSwo1V0doVVBZek9aNDFERmZ1NTJiZzI1TzgxekFiRUhaS1J3SURBUUFCTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCCkFRQWJ1dGZjbENOZVhTaldBR0NSb2tyTVN2Z0VvMlZEdnE4Y1lEN3hIT3gzRllQRWE0Rk01VC9uSXVsNGJxSCsKY09mOCtMOTZXTGNUUnpNRnhrMmNKT2VKV3hFMDkzcDN2dHRZMFUrOGZ4T1FIY3JxK1N3U0dPTUpWTHhEcGtPNApscFVpc0JYOENGQld5VG9vN05WRy9FZGRVS1FHa2ttaGJMdXJIZStHTnFmT0VpS01GYm1PRHBzZ1Zqc0oxK2hPCjZDWG8zZW01Mlh4eVZqbGtoNzBJK29UMW5PeGFYSEhwK0NNT2JPSXkzcFhMejJROWNmRU1uTlZrVTBDMmFaeksKS1ViMGZXOTlpbjBJRmlUd0NkQlhRTlRpMzh6bVEvUUlEYlJEQTJFREtMa2pZRzdUUFR4Wm9xL04rQUQ3ZElLWQpyaE56TXB6cGhhRGR4Ymt3cmlHRGQ5TEkKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==` + serverKey = `LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBemdMMTJpL0gwaXk5MGhLb0sySUwrbnIvRDB0RGlPdVlwYk5NUktCTTJyMGtCblhCClJUdU1peWNWMUd5OGlwQ1FLMmwwV1E3dDJpTStLT1BBTFYxQ0thNEtpTlg5M015cWlmVFVBc3YzemRtNkhiZFMKa3owcUlOYTBoZTBaclA3M3g0YWZHRmJ2SXRwU1pWQThwSUhrc281SHFhcTRndTcwVUhRSlhZcFIrRjlKY3l3eAppWTZ4S0x6QWNGcjlsdktZdlVaWitRR1FUbWh4TFFtKzI5cUJsZXIvRGpEQzlkZjM2blFkSU5wdUpIak1tZXI2CnZQVU5CekQ5OWliT3NpYVBxMlo1VHRuY3U0TlRzKzlLN09BWnhLK1d4VVp6c3pKbnhHNjgxR010MXJ1anBIVlMKSGIrYmh2ek9aRmt6aFhWVE5wL0FYMElNdy92K3hpZzl3WXkyK1FJREFRQUJBb0lCQVFESmFObUdWRnB1NERGQgpGZDUyYzZnMFhsWEpWUk1VNVFsYlR2MU14cy84dHhobWZHL1ZTUS94NStlT3hEUmM0Rk1qTGptQzdIYWNZd0pkCnBiVDRaUW5QaUFsaW1KeFdaMzUvMiszL1Fmem1zMndqcTF3KytYaWJuRzNuMWRQWmIzaytDQjY1QkIxT0hOYWIKbUtPQlRrRVNWTW81VmVDSW1pZ2dGQ0luNHBpYlVvSXpHcmdabVRsTmlQTjFPb2FNNk9IcEorZDVGNDFNdGYwbgpJYjAyTVYzdUtZb1hqUWtFYytBL3B3WDJ0TVlGUTMza3NwTlZjSURnYU4zUWRsb3IxQXhDN0xQaStKeTZNelY1CmJ4VDhlcFhZN2VmUFhqblR0VmVHQWFUNThJSFdWS29ncmt6V21xdHROMUtzc2RBWGpja1NWRksxL0U5V1c4Q28KWDBTa3VpUFJBb0dCQVA1SERCSDZkRURqbVp0TVVFeHEwNWlwcWxSbFhnYUdYT01lT0VIZ1VSVzhEdnJiNC9zLwo4cEswUWZCUUpkYTIwT1d0dVprSDNYWEVnNktjL0NyMWZIQU9uNElmOFI1NlJNRUpWUnhvTzlBZWZVem9nZjAvCndWUlpmZmRUTkhKV2E3VExJaVdNdXpmY2k0bTdldnZkNkpUS3lzTlRvREJuemM0ZFpzcGhxWHh0QW9HQkFNOW8KTnBqSmxsZDBrUENicFRtRjZMRGpBMzAvaS9DRzlVNEoxaUFrQno3SzA2TU9ENDA3TlZ5QTV0V2p5QjlSTThnbwpMcjZhK1g3MDN2YWVMZDZQcDY2UnlDbzU4RWFhcFk2SFQvTGVNSGRzUllEdk9PU2pOT0FtbkJxcFFVR1Z2ZVhTClpCN2srclpVK3g2UktEblFod3crZGsveUZkZmJ5VmxhTDRKVFZiVTlBb0dCQVBOYUNYbzNTUVZGRGFBc0EvbHUKajMxZWUwMzBDVzJUTDlpSTlteE5neXlhNDNjLzlNdGpZd0wyRXRrcnkxclhjY3N1WFI3UkFTaVJYeTNFc2kxbQo3YVhNeU9sZktvTHhuMVZqV2hvcXczdWxnbU9WYmJweVJ0TTBKck1KNVhxN3JLN0ZiYk9rSVJVUU5GY25uMGJuCkZJMDZHNTJlTGdQRmhKaUxXUEc5VDlodEFvR0JBS1JrWEluamxqZEJYRFJwbVo4clpWRDJ6bWd5dXc5dFdQZCsKMG1wdFJCVGdITGtyeHVYUlhTMHh1a1R4YVFoeGkxS0ZqdTlpMUlodFBHQks1ZDUzREpoUVVsQXQxaVdRSTlNQgpxenU4SXJ3MVpDMmE3d1JCM0FJaWVDNmxvdVNCOUo4NWtFUHdpRXVHdGZmM1krUFhSWU5ONnViWTRibFRLcGVZCjVQa3Vaa3VkQW9HQVozTHg0UGNnK3RSR0dsclR0WllXZmNxT3FDcUIya0NCa2tDVnlvWW9qSzM4a21CNVJ0QmQKZzBnMTc5TGdUTWE0Y0ZWRUhyZC9xU2RRQVZiTmxyRDh4MjJNYnJMam1aa05aOXQ1alpJSWQwRVRxYTJBNC8rNApOV05HeU12b0ttTmMxcFd2UEJiT1R1RHp2WEg3YnZzbXAzallucjJQU09WaFU1RGdJRHpEQTJBPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQ==` + serverCrt = `LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMzekNDQWNlZ0F3SUJBZ0lKQU55SVoyTElxQTNqTUEwR0NTcUdTSWIzRFFFQkJRVUFNQmt4RnpBVkJnTlYKQkFNTURpb3VhMlYxWW1WMlpXeGhMbWx2TUI0WERUSXhNVEV5TWpBNU16UXpORm9YRFRNMU1EZ3dNVEE1TXpRegpORm93R0RFV01CUUdBMVVFQXd3TktpNXJkV0psZG1Wc1lTNXBiekNDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFECmdnRVBBRENDQVFvQ2dnRUJBTTRDOWRvdng5SXN2ZElTcUN0aUMvcDYvdzlMUTRqcm1LV3pURVNnVE5xOUpBWjEKd1VVN2pJc25GZFJzdklxUWtDdHBkRmtPN2RvalBpamp3QzFkUWltdUNvalYvZHpNcW9uMDFBTEw5ODNadWgyMwpVcE05S2lEV3RJWHRHYXorOThlR254aFc3eUxhVW1WUVBLU0I1TEtPUjZtcXVJTHU5RkIwQ1YyS1VmaGZTWE1zCk1ZbU9zU2k4d0hCYS9aYnltTDFHV2ZrQmtFNW9jUzBKdnR2YWdaWHEvdzR3d3ZYWDkrcDBIU0RhYmlSNHpKbnEKK3J6MURRY3cvZlltenJJbWo2dG1lVTdaM0x1RFU3UHZTdXpnR2NTdmxzVkdjN015WjhSdXZOUmpMZGE3bzZSMQpVaDIvbTRiOHptUlpNNFYxVXphZndGOUNETVA3L3NZb1BjR010dmtDQXdFQUFhTXJNQ2t3Q1FZRFZSMFRCQUl3CkFEQUxCZ05WSFE4RUJBTUNCZUF3RHdZRFZSMFJCQWd3Qm9jRWZ3QUFBVEFOQmdrcWhraUc5dzBCQVFVRkFBT0MKQVFFQUJNbUhSZG4rS043QWZTL3JicHI2dGx6SHBoRFJud29KR0NxSkZYZjdabUN1TEF3NzVsTlhxOUxka2NJeApSZXhleXk2cnk2SmF6RGN4OVltWHVnZzFtTTlrWE5kTmc0NmVSangzRk4vL2FRUFJOMHNuTDVOaXRyM0kvdEJmCkxNdlduUisrQ2tZSnFtM1NuTnRicVR0cDhodTZKWnVRUVh2WWM0ZEg5VmJRM2d3UzFSUzdhQ0RBZHlLZEhnSFQKZmN3VnNqZmk2TzhLdlROaG43aU1LWERZQUhiWXh3ekpsdjBEWFhuZjhmRlo3U09FT3VkbGM5Y3hOQW0xVlBjZAo1YXcwR0hvbWMxNTdHU244UmpWenlJOHRPdGE4WU9uaU9SOE5qNElzMExRQXF3VjJ1OU52Zkg3bkJMUjg3d1Z2CmFxUmxaWE5uOG5qcWgwdzZkc3BUWjRwQWFBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==` + clientCrt = `LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5VENDQWJHZ0F3SUJBZ0lKQU55SVoyTElxQTNpTUEwR0NTcUdTSWIzRFFFQkJRVUFNQmt4RnpBVkJnTlYKQkFNTURpb3VhMlYxWW1WMlpXeGhMbWx2TUI0WERUSXhNVEV5TWpBNE5UVTFNRm9YRFRNMU1EZ3dNVEE0TlRVMQpNRm93RmpFVU1CSUdBMVVFQXd3TGRHVnpkRjlzYVdwcFlXNHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCCkR3QXdnZ0VLQW9JQkFRQzhZNGU1ZElYd3FaL1dWOFVLMEhVc2toRlRmTytxMC96VU9CZVl0amplNFQrTmhlc2oKS0tkLzhWLzBYZTY5ck5CYXE3RVBhYUFReDVYYTF1aGh2SWRXU2F2QWhmaTgwd3lXSy9reUtKR0xsaTZleTZtLwpjZkdxRVJ5eUtnc2NoZWFrQ3dCajArZkxnVUZ5aVM0MzVubnVZZ2Y0MXFmRVJ1azREUExDVk9uRE13dXBmQkdyClNacmY2U0R3Z0V3SnV2QVlhQ0owcE91NkovRzhkMVNOb3I4UFlGQjBFbGl2d2ZESk9CRjFvMEg2elYzMHZ1bVoKRzM0anpoNjh3aThPNG1TZjZCbi9XMlEyckNvY1FlSE9nUVRGcHVtdC9qYy9lMnhlRFpOd3RpS25OMTdWa3o1QwpXVGxpUEZuQlRWTWZCeHEyOGNIemtzaTVBRDZ5bVlRMkNzcTVBZ01CQUFHakZ6QVZNQk1HQTFVZEpRUU1NQW9HCkNDc0dBUVVGQndNQ01BMEdDU3FHU0liM0RRRUJCUVVBQTRJQkFRQnpKRnFlUk9BRDZ0ZkVrNElsNGxvbDI4OGIKaFZMMnRXdThXbGtDdHFQaFNOR0hkeWJQcGdLL3dCajQzS3FGcFRMVGo0TStDT0cwR08xZDVMK1lROHdHOHJGQQpHWTd3ZndQLzRlenpzSzNocmI5NnNpdm04TUZqdXRzSEdzenFWRkZ0UXBNWkhBTm5FQXY0ZkxGSEtQM0ZubmkyCnpjYmwrVXNQWFk3QU5NelpOelIwQVdLWmxwbm5hMUpuQWtzQnBBTzlweFRKOU55MzhVNlc0SERrN2gyVk5BUHAKbGpxRmNoYXdjTkN1MDIzV2hhWWxuNGowTG9NRlh0NDJNMXgxL2R4SnQxNUlnNFB5LysrbmZRbmtvN09vSmVpVAppb0lNc3VBcmNJaG1MSU8zZzFTNVJtNzJ6NDUwSXV0blFWQUc3MVQ0alZyR3libHhnMWpGVjFXWHJ1V2MKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==` + clientKey = `LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdkdPSHVYU0Y4S21mMWxmRkN0QjFMSklSVTN6dnF0UDgxRGdYbUxZNDN1RS9qWVhyCkl5aW5mL0ZmOUYzdXZhelFXcXV4RDJtZ0VNZVYydGJvWWJ5SFZrbXJ3SVg0dk5NTWxpdjVNaWlSaTVZdW5zdXAKdjNIeHFoRWNzaW9MSElYbXBBc0FZOVBueTRGQmNva3VOK1o1N21JSCtOYW54RWJwT0F6eXdsVHB3ek1McVh3UgpxMG1hMytrZzhJQk1DYnJ3R0dnaWRLVHJ1aWZ4dkhkVWphSy9EMkJRZEJKWXI4SHd5VGdSZGFOQitzMWQ5TDdwCm1SdCtJODRldk1JdkR1SmtuK2daLzF0a05xd3FIRUhoem9FRXhhYnByZjQzUDN0c1hnMlRjTFlpcHpkZTFaTSsKUWxrNVlqeFp3VTFUSHdjYXR2SEI4NUxJdVFBK3NwbUVOZ3JLdVFJREFRQUJBb0lCQUhYYjZ1aTZucVUrNmRHbQpYWTd6ZGFzcHd3OHhaWnZCUGpiaTFOaGtnRlhvSStOOWVlc29Id3FyVHZYSjRuZmw2d0FlMUFvcGNjdXRvZklrCmE0UGg5K1dpOTRIZUR3ekxHTi9HcVFPWlg5MHRXd05idFZvaGhpaDR4alFzbTREL3dKaTJqVXJuSXVndGVHMlkKcDBLdnZXN0hBK2ZKRzNKdlRxOFRZcmp6ZUwvMlYzUnRSbk1oNldjcWI0cWpRb2NZWkR3VU43MVBVYURrUTRLSApWdHNNSjloc0dBUDFDVFRQckZZR1daRUZiN0xtZGNRemJyWVZyQnhwaGRoZGpqUXBjVzAySjZHU1ZrY0NMeFkvCnVRYnpZS0RkUXU1SHJKbVpiNjlFRmJWZHp3OTh3alIvNyt0dUhmdlJzYkUxRWdKWlQxNkhSUHEwbW1mMWxHM3EKNm9ZbnNCRUNnWUVBNVd2SEE4bWliMVpYWiszaXN3SUh0SlZ5WGJWM0Y4ejZ5UHRaK2ZNa1ljY3BBb3g0SDRTbgp1azllN2NlN0lhMDJueXlwTmlIaXZMRXZ3dGlqNjlBQ3I1WWpMMzlDMUlYbjJYMUg3V2FKaHF2K21zOW5ybmV0CmRqY0RtZXJRRHBVU0IzbXFLQlhXY2dVaXA4NUVYMlo5SzhPbnRhNXV6MG8vb2R4a0VTdWM3M1VDZ1lFQTBqYlAKUVBjY1N4RmRsVDFEcjg0WWhkUDJzQUJ6YUpoWDNzUm44NnhBTWk4aW9PY05lMTh1cVFBdk4rQWpRQUFndlkyOQo3dVdRcXB1SlFueG9NcGJMVjdjSkw2aHBlekpEL2lhZDVzREpyMC9jTWVhK3JSWVFQK2xxSW9YZTI2TlZPZnNHCmZGNkpHZUdvUGpRTmVKM1FSV0NaMEo0UkRob242YjZCWHVFVTZiVUNnWUVBczlCRGpiNXQ1K0crWkNEWlBBQnQKVmFhRW10bnQyK08yOCt1OVcrQ3NOVTdKMzh1Rkl2N3dEMkRDUUkvNUphNERUOExMWlRndDVFTGo4azJtUE44dQpHNzBMR3VFZDJrQ1J0YTh4dnVwTkJCYXVXVndTSVhaL3FGWDZKcHNhTXpPM2k5QmFBMDBLWlJlTlVBU2xKampJCkJwTTFVWHJFTXdnNDAzNVBsLzJjNVRrQ2dZQnRmL054cWNicEs0Q043cjNGWkJ2T0NsMmp6SGhSY1puRUJwY0gKalNCYmc4WUwvbzg5UnBWdG54VDVqQjJRaHdDRy9NQ0ZJcnU2d3c0NnZjY2hJditGRDJrUGxEQnQ1ZjhZOGxDcQpGSjU2WGFVYnNWQjlwTktPR0M0YkVaVEc0RXZTeWZuVTZ3R0xvOG9ack0rZmxzVVlmbnRnK2hWMFBSZXhZSFRQClVYdXRTUUtCZ1FDck9WeHBqNXFKdmMyaUZrMXNoWDRXeDBYbHpzZzI3QkdUNy9zYmtrMHMxT1ViZEZueTErTUkKNUVpVU1xUHM5TU5IOWZxbHNmMEJCS1BXMW0wVFAvSHo2OHFrRm80cnJrZVlMYmYvYVN2OWFJNnRodGJTWUoyUQpKTm9qeW1Ea2ZFbmNDOTNsMUV5alF0Y1lJSGNDWFRGMlhibXVwdEtlT2lqeC84c3FTOUVkRmc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQ==` + MockCerts = struct { + Ca string + ServerKey string + ServerCrt string + ClientCrt string + ClientKey string + }{ + Ca: caCrt, + ServerCrt: serverCrt, + ServerKey: serverKey, + ClientKey: clientKey, + ClientCrt: clientCrt, + } +) diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/generator.go b/pkg/controller/core.oam.dev/v1alpha2/application/generator.go index 14886919f..8ac2015b8 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/generator.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/generator.go @@ -20,6 +20,8 @@ import ( "encoding/json" "strings" + "github.com/oam-dev/kubevela/pkg/workflow/providers/http" + "github.com/pkg/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/controller-runtime/pkg/client" @@ -53,6 +55,7 @@ func (h *AppHandler) GenerateApplicationSteps(ctx context.Context, kube.Install(handlerProviders, h.r.Client, h.Dispatch, h.Delete) oamProvider.Install(handlerProviders, app, h.applyComponentFunc( appParser, appRev, af), h.renderComponentFunc(appParser, appRev, af)) + http.Install(handlerProviders, h.r.Client, app.Namespace) taskDiscover := tasks.NewTaskDiscover(handlerProviders, h.r.pd, h.r.Client, h.r.dm) multiclusterProvider.Install(handlerProviders, h.r.Client, app) terraformProvider.Install(handlerProviders, app, func(comp common.ApplicationComponent) (*appfile.Workload, error) { diff --git a/pkg/stdlib/pkgs/http.cue b/pkg/stdlib/pkgs/http.cue index 537449cc5..e18dc69cb 100644 --- a/pkg/stdlib/pkgs/http.cue +++ b/pkg/stdlib/pkgs/http.cue @@ -9,6 +9,7 @@ header: [string]: string trailer: [string]: string } + tls_config?: secret: string response: { body: string header?: [string]: [...string] diff --git a/pkg/workflow/providers/http/do.go b/pkg/workflow/providers/http/do.go index c2fa41336..a3edaa871 100644 --- a/pkg/workflow/providers/http/do.go +++ b/pkg/workflow/providers/http/do.go @@ -17,7 +17,13 @@ limitations under the License. package http import ( + "context" + "encoding/base64" + "strings" + "cuelang.org/go/cue" + v1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" "github.com/oam-dev/kubevela/pkg/builtin" "github.com/oam-dev/kubevela/pkg/builtin/registry" @@ -33,10 +39,61 @@ const ( ) type provider struct { + cli client.Client + ns string } // Do process http request. func (h *provider) Do(ctx wfContext.Context, v *value.Value, act types.Action) error { + tlsConfig, err := v.LookupValue("tls_config") + if err == nil { + secretName, err := tlsConfig.GetString("secret") + if err != nil { + return err + } + objectKey := client.ObjectKey{ + Namespace: h.ns, + Name: secretName, + } + index := strings.Index(secretName, "/") + if index > 0 { + objectKey.Namespace = secretName[:index-1] + objectKey.Name = secretName[index:] + } + + secret := new(v1.Secret) + if err := h.cli.Get(context.Background(), objectKey, secret); err != nil { + return err + } + if ca, ok := secret.Data["ca.crt"]; ok { + caData, err := base64.StdEncoding.DecodeString(string(ca)) + if err != nil { + return err + } + if err := v.FillObject(string(caData), "tls_config", "ca"); err != nil { + return err + } + } + if clientCert, ok := secret.Data["client.crt"]; ok { + certData, err := base64.StdEncoding.DecodeString(string(clientCert)) + if err != nil { + return err + } + if err := v.FillObject(string(certData), "tls_config", "client_crt"); err != nil { + return err + } + } + + if clientKey, ok := secret.Data["client.key"]; ok { + keyData, err := base64.StdEncoding.DecodeString(string(clientKey)) + if err != nil { + return err + } + if err := v.FillObject(string(keyData), "tls_config", "client_key"); err != nil { + return err + } + } + } ret, err := builtin.RunTaskByKey("http", cue.Value{}, ®istry.Meta{ Obj: v.CueValue(), }) @@ -47,8 +104,11 @@ func (h *provider) Do(ctx wfContext.Context, v *value.Value, act types.Action) e } // Install register handlers to provider discover. -func Install(p providers.Providers) { - prd := &provider{} +func Install(p providers.Providers, cli client.Client, ns string) { + prd := &provider{ + cli: cli, + ns: ns, + } p.Register(ProviderName, map[string]providers.Handler{ "do": prd.Do, }) diff --git a/pkg/workflow/providers/http/do_test.go b/pkg/workflow/providers/http/do_test.go index bf255cb15..0a0f6fa6f 100644 --- a/pkg/workflow/providers/http/do_test.go +++ b/pkg/workflow/providers/http/do_test.go @@ -17,13 +17,25 @@ limitations under the License. package http import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/base64" + "encoding/json" + "fmt" "io" + "net" "net/http" + "net/http/httptest" "testing" "time" + "github.com/crossplane/crossplane-runtime/pkg/test" "gotest.tools/assert" + v1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/oam-dev/kubevela/pkg/builtin/http/testdata" "github.com/oam-dev/kubevela/pkg/cue/model/value" "github.com/oam-dev/kubevela/pkg/workflow/providers" ) @@ -102,7 +114,7 @@ request:{ func TestInstall(t *testing.T) { p := providers.NewProviders() - Install(p) + Install(p, nil, "") h, ok := p.GetHandler("http", "do") assert.Equal(t, ok, true) assert.Equal(t, h != nil, true) @@ -134,3 +146,68 @@ func runMockServer(shutdown chan struct{}) { } } } + +func TestHTTPSDo(t *testing.T) { + s := newMockHttpsServer() + defer s.Close() + cli := &test.MockClient{ + MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { + secret := obj.(*v1.Secret) + *secret = v1.Secret{ + Data: map[string][]byte{ + "ca.crt": []byte(testdata.MockCerts.Ca), + "client.crt": []byte(testdata.MockCerts.ClientCrt), + "client.key": []byte(testdata.MockCerts.ClientKey), + }, + } + return nil + }, + } + v, err := value.NewValue(` +method: "GET" +url: "https://127.0.0.1:8443/api/v1/token?val=test-token" +`, nil, "") + assert.NilError(t, err) + assert.NilError(t, v.FillObject("certs", "tls_config", "secret")) + prd := &provider{cli, "default"} + err = prd.Do(nil, v, nil) + assert.NilError(t, err) + +} + +func newMockHttpsServer() *httptest.Server { + ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + fmt.Printf("Expected 'GET' request, got '%s'", r.Method) + } + if r.URL.EscapedPath() != "/api/v1/token" { + fmt.Printf("Expected request to '/person', got '%s'", r.URL.EscapedPath()) + } + r.ParseForm() + token := r.Form.Get("val") + tokenBytes, _ := json.Marshal(map[string]interface{}{"token": token}) + + w.WriteHeader(http.StatusOK) + w.Write(tokenBytes) + })) + l, _ := net.Listen("tcp", "127.0.0.1:8443") + ts.Listener.Close() + ts.Listener = l + + decode := func(in string) []byte { + out, _ := base64.StdEncoding.DecodeString(in) + return out + } + + pool := x509.NewCertPool() + pool.AppendCertsFromPEM(decode(testdata.MockCerts.Ca)) + cert, _ := tls.X509KeyPair(decode(testdata.MockCerts.ServerCrt), decode(testdata.MockCerts.ServerKey)) + ts.TLS = &tls.Config{ + ClientCAs: pool, + ClientAuth: tls.RequireAndVerifyClientCert, + Certificates: []tls.Certificate{cert}, + NextProtos: []string{"http/1.1"}, + } + ts.StartTLS() + return ts +} diff --git a/pkg/workflow/tasks/discover.go b/pkg/workflow/tasks/discover.go index fb0f5d88b..37da1732b 100644 --- a/pkg/workflow/tasks/discover.go +++ b/pkg/workflow/tasks/discover.go @@ -76,7 +76,6 @@ func suspend(step v1beta1.WorkflowStep, opt *types.GeneratorOptions) (types.Task func NewTaskDiscover(providerHandlers providers.Providers, pd *packages.PackageDiscover, cli client.Client, dm discoverymapper.DiscoveryMapper) types.TaskDiscover { // install builtin provider workspace.Install(providerHandlers) - http.Install(providerHandlers) convert.Install(providerHandlers) email.Install(providerHandlers) templateLoader := template.NewWorkflowStepTemplateLoader(cli, dm) @@ -122,7 +121,7 @@ func NewViewTaskDiscover(pd *packages.PackageDiscover, cli client.Client, apply query.Install(handlerProviders, cli) time.Install(handlerProviders) kube.Install(handlerProviders, cli, apply, delete) - http.Install(handlerProviders) + http.Install(handlerProviders, cli, viewNs) convert.Install(handlerProviders) email.Install(handlerProviders)