Merge pull request #184 from fluxcd/feature/self-signed-certs
Add self signed cert to provider
This commit is contained in:
		
						commit
						490c9550c9
					
				|  | @ -56,6 +56,11 @@ type ProviderSpec struct { | ||||||
| 	// using "address" as data key
 | 	// using "address" as data key
 | ||||||
| 	// +optional
 | 	// +optional
 | ||||||
| 	SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"` | 	SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"` | ||||||
|  | 
 | ||||||
|  | 	// CertSecretRef can be given the name of a secret containing
 | ||||||
|  | 	// a PEM-encoded CA certificate (`caFile`)
 | ||||||
|  | 	// +optional
 | ||||||
|  | 	CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
|  |  | ||||||
|  | @ -215,6 +215,11 @@ func (in *ProviderSpec) DeepCopyInto(out *ProviderSpec) { | ||||||
| 		*out = new(meta.LocalObjectReference) | 		*out = new(meta.LocalObjectReference) | ||||||
| 		**out = **in | 		**out = **in | ||||||
| 	} | 	} | ||||||
|  | 	if in.CertSecretRef != nil { | ||||||
|  | 		in, out := &in.CertSecretRef, &out.CertSecretRef | ||||||
|  | 		*out = new(meta.LocalObjectReference) | ||||||
|  | 		**out = **in | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderSpec.
 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderSpec.
 | ||||||
|  |  | ||||||
|  | @ -50,6 +50,16 @@ spec: | ||||||
|                 description: HTTP/S webhook address of this provider |                 description: HTTP/S webhook address of this provider | ||||||
|                 pattern: ^(http|https):// |                 pattern: ^(http|https):// | ||||||
|                 type: string |                 type: string | ||||||
|  |               certSecretRef: | ||||||
|  |                 description: CertSecretRef can be given the name of a secret containing | ||||||
|  |                   a PEM-encoded CA certificate (`caFile`) | ||||||
|  |                 properties: | ||||||
|  |                   name: | ||||||
|  |                     description: Name of the referent | ||||||
|  |                     type: string | ||||||
|  |                 required: | ||||||
|  |                 - name | ||||||
|  |                 type: object | ||||||
|               channel: |               channel: | ||||||
|                 description: Alert channel for this provider |                 description: Alert channel for this provider | ||||||
|                 type: string |                 type: string | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ package controllers | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"crypto/x509" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | @ -121,7 +122,28 @@ func (r *ProviderReconciler) validate(ctx context.Context, provider v1beta1.Prov | ||||||
| 		return fmt.Errorf("no address found in 'spec.address' nor in `spec.secretRef`") | 		return fmt.Errorf("no address found in 'spec.address' nor in `spec.secretRef`") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	factory := notifier.NewFactory(address, provider.Spec.Proxy, provider.Spec.Username, provider.Spec.Channel, token) | 	var certPool *x509.CertPool | ||||||
|  | 	if provider.Spec.CertSecretRef != nil { | ||||||
|  | 		var secret corev1.Secret | ||||||
|  | 		secretName := types.NamespacedName{Namespace: provider.Namespace, Name: provider.Spec.CertSecretRef.Name} | ||||||
|  | 
 | ||||||
|  | 		if err := r.Get(ctx, secretName, &secret); err != nil { | ||||||
|  | 			return fmt.Errorf("failed to read secret, error: %w", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		caFile, ok := secret.Data["caFile"] | ||||||
|  | 		if !ok { | ||||||
|  | 			return fmt.Errorf("no caFile found in secret %s", provider.Spec.CertSecretRef.Name) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		certPool = x509.NewCertPool() | ||||||
|  | 		ok = certPool.AppendCertsFromPEM(caFile) | ||||||
|  | 		if !ok { | ||||||
|  | 			return fmt.Errorf("could not append to cert pool: invalid CA found in %s", provider.Spec.CertSecretRef.Name) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	factory := notifier.NewFactory(address, provider.Spec.Proxy, provider.Spec.Username, provider.Spec.Channel, token, certPool) | ||||||
| 	if _, err := factory.Notifier(provider.Spec.Type); err != nil { | 	if _, err := factory.Notifier(provider.Spec.Type); err != nil { | ||||||
| 		return fmt.Errorf("failed to initialise provider, error: %w", err) | 		return fmt.Errorf("failed to initialise provider, error: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -298,6 +298,21 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference | ||||||
| using “address” as data key</p> | using “address” as data key</p> | ||||||
| </td> | </td> | ||||||
| </tr> | </tr> | ||||||
|  | <tr> | ||||||
|  | <td> | ||||||
|  | <code>certSecretRef</code><br> | ||||||
|  | <em> | ||||||
|  | <a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#LocalObjectReference"> | ||||||
|  | github.com/fluxcd/pkg/apis/meta.LocalObjectReference | ||||||
|  | </a> | ||||||
|  | </em> | ||||||
|  | </td> | ||||||
|  | <td> | ||||||
|  | <em>(Optional)</em> | ||||||
|  | <p>CertSecretRef can be given the name of a secret containing | ||||||
|  | a PEM-encoded CA certificate (<code>caFile</code>)</p> | ||||||
|  | </td> | ||||||
|  | </tr> | ||||||
| </table> | </table> | ||||||
| </td> | </td> | ||||||
| </tr> | </tr> | ||||||
|  | @ -761,6 +776,21 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference | ||||||
| using “address” as data key</p> | using “address” as data key</p> | ||||||
| </td> | </td> | ||||||
| </tr> | </tr> | ||||||
|  | <tr> | ||||||
|  | <td> | ||||||
|  | <code>certSecretRef</code><br> | ||||||
|  | <em> | ||||||
|  | <a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#LocalObjectReference"> | ||||||
|  | github.com/fluxcd/pkg/apis/meta.LocalObjectReference | ||||||
|  | </a> | ||||||
|  | </em> | ||||||
|  | </td> | ||||||
|  | <td> | ||||||
|  | <em>(Optional)</em> | ||||||
|  | <p>CertSecretRef can be given the name of a secret containing | ||||||
|  | a PEM-encoded CA certificate (<code>caFile</code>)</p> | ||||||
|  | </td> | ||||||
|  | </tr> | ||||||
| </tbody> | </tbody> | ||||||
| </table> | </table> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|  | @ -209,3 +209,16 @@ The body of the request looks like this: | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| The `involvedObject` key contains the object that triggered the event. | The `involvedObject` key contains the object that triggered the event. | ||||||
|  | 
 | ||||||
|  | ### Self signed certificates | ||||||
|  | 
 | ||||||
|  | The `certSecretRef` field names a secret with TLS certificate data. This is for the purpose | ||||||
|  | of enabling a provider to communicate with a server using a self signed cert. | ||||||
|  | 
 | ||||||
|  | To use the field create a secret, containing a CA file, in the same namespace and reference | ||||||
|  | it from the provider. | ||||||
|  | ```shell | ||||||
|  | SECRET_NAME=tls-certs | ||||||
|  | kubectl create secret generic $SECRET_NAME \ | ||||||
|  |   --from-file=caFile=ca.crt | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | @ -18,12 +18,15 @@ package notifier | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"crypto/x509" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/fluxcd/pkg/runtime/events" |  | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/fluxcd/pkg/runtime/events" | ||||||
|  | 
 | ||||||
| 	"github.com/microsoft/azure-devops-go-api/azuredevops" | 	"github.com/microsoft/azure-devops-go-api/azuredevops" | ||||||
| 	"github.com/microsoft/azure-devops-go-api/azuredevops/git" | 	"github.com/microsoft/azure-devops-go-api/azuredevops/git" | ||||||
| ) | ) | ||||||
|  | @ -38,7 +41,7 @@ type AzureDevOps struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewAzureDevOps creates and returns a new AzureDevOps notifier.
 | // NewAzureDevOps creates and returns a new AzureDevOps notifier.
 | ||||||
| func NewAzureDevOps(addr string, token string) (*AzureDevOps, error) { | func NewAzureDevOps(addr string, token string, certPool *x509.CertPool) (*AzureDevOps, error) { | ||||||
| 	if len(token) == 0 { | 	if len(token) == 0 { | ||||||
| 		return nil, errors.New("azure devops token cannot be empty") | 		return nil, errors.New("azure devops token cannot be empty") | ||||||
| 	} | 	} | ||||||
|  | @ -58,6 +61,11 @@ func NewAzureDevOps(addr string, token string) (*AzureDevOps, error) { | ||||||
| 
 | 
 | ||||||
| 	orgURL := fmt.Sprintf("%v/%v", host, org) | 	orgURL := fmt.Sprintf("%v/%v", host, org) | ||||||
| 	connection := azuredevops.NewPatConnection(orgURL, token) | 	connection := azuredevops.NewPatConnection(orgURL, token) | ||||||
|  | 	if certPool != nil { | ||||||
|  | 		connection.TlsConfig = &tls.Config{ | ||||||
|  | 			RootCAs: certPool, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	client := connection.GetClientByUrl(orgURL) | 	client := connection.GetClientByUrl(orgURL) | ||||||
| 	gitClient := &git.ClientImpl{ | 	gitClient := &git.ClientImpl{ | ||||||
| 		Client: *client, | 		Client: *client, | ||||||
|  |  | ||||||
|  | @ -24,19 +24,19 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestNewAzureDevOpsBasic(t *testing.T) { | func TestNewAzureDevOpsBasic(t *testing.T) { | ||||||
| 	a, err := NewAzureDevOps("https://dev.azure.com/foo/bar/_git/baz", "foo") | 	a, err := NewAzureDevOps("https://dev.azure.com/foo/bar/_git/baz", "foo", nil) | ||||||
| 	assert.Nil(t, err) | 	assert.Nil(t, err) | ||||||
| 	assert.Equal(t, a.Project, "bar") | 	assert.Equal(t, a.Project, "bar") | ||||||
| 	assert.Equal(t, a.Repo, "baz") | 	assert.Equal(t, a.Repo, "baz") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestNewAzureDevOpsInvalidUrl(t *testing.T) { | func TestNewAzureDevOpsInvalidUrl(t *testing.T) { | ||||||
| 	_, err := NewAzureDevOps("https://dev.azure.com/foo/bar/baz", "foo") | 	_, err := NewAzureDevOps("https://dev.azure.com/foo/bar/baz", "foo", nil) | ||||||
| 	assert.NotNil(t, err) | 	assert.NotNil(t, err) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestNewAzureDevOpsMissingToken(t *testing.T) { | func TestNewAzureDevOpsMissingToken(t *testing.T) { | ||||||
| 	_, err := NewAzureDevOps("https://dev.azure.com/foo/bar/baz", "") | 	_, err := NewAzureDevOps("https://dev.azure.com/foo/bar/baz", "", nil) | ||||||
| 	assert.NotNil(t, err) | 	assert.NotNil(t, err) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -17,8 +17,11 @@ limitations under the License. | ||||||
| package notifier | package notifier | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"crypto/x509" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/fluxcd/pkg/runtime/events" | 	"github.com/fluxcd/pkg/runtime/events" | ||||||
|  | @ -33,7 +36,7 @@ type Bitbucket struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewBitbucket creates and returns a new Bitbucket notifier.
 | // NewBitbucket creates and returns a new Bitbucket notifier.
 | ||||||
| func NewBitbucket(addr string, token string) (*Bitbucket, error) { | func NewBitbucket(addr string, token string, certPool *x509.CertPool) (*Bitbucket, error) { | ||||||
| 	if len(token) == 0 { | 	if len(token) == 0 { | ||||||
| 		return nil, errors.New("bitbucket token cannot be empty") | 		return nil, errors.New("bitbucket token cannot be empty") | ||||||
| 	} | 	} | ||||||
|  | @ -57,10 +60,21 @@ func NewBitbucket(addr string, token string) (*Bitbucket, error) { | ||||||
| 	owner := comp[0] | 	owner := comp[0] | ||||||
| 	repo := comp[1] | 	repo := comp[1] | ||||||
| 
 | 
 | ||||||
|  | 	client := bitbucket.NewBasicAuth(username, password) | ||||||
|  | 	if certPool != nil { | ||||||
|  | 		tr := &http.Transport{ | ||||||
|  | 			TLSClientConfig: &tls.Config{ | ||||||
|  | 				RootCAs: certPool, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		hc := &http.Client{Transport: tr} | ||||||
|  | 		client.HttpClient = hc | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return &Bitbucket{ | 	return &Bitbucket{ | ||||||
| 		Owner:  owner, | 		Owner:  owner, | ||||||
| 		Repo:   repo, | 		Repo:   repo, | ||||||
| 		Client: bitbucket.NewBasicAuth(username, password), | 		Client: client, | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -23,18 +23,18 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestNewBitbucketBasic(t *testing.T) { | func TestNewBitbucketBasic(t *testing.T) { | ||||||
| 	b, err := NewBitbucket("https://bitbucket.org/foo/bar", "foo:bar") | 	b, err := NewBitbucket("https://bitbucket.org/foo/bar", "foo:bar", nil) | ||||||
| 	assert.Nil(t, err) | 	assert.Nil(t, err) | ||||||
| 	assert.Equal(t, b.Owner, "foo") | 	assert.Equal(t, b.Owner, "foo") | ||||||
| 	assert.Equal(t, b.Repo, "bar") | 	assert.Equal(t, b.Repo, "bar") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestNewBitbucketInvalidUrl(t *testing.T) { | func TestNewBitbucketInvalidUrl(t *testing.T) { | ||||||
| 	_, err := NewBitbucket("https://bitbucket.org/foo/bar/baz", "foo:bar") | 	_, err := NewBitbucket("https://bitbucket.org/foo/bar/baz", "foo:bar", nil) | ||||||
| 	assert.NotNil(t, err) | 	assert.NotNil(t, err) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestNewBitbucketInvalidToken(t *testing.T) { | func TestNewBitbucketInvalidToken(t *testing.T) { | ||||||
| 	_, err := NewBitbucket("https://bitbucket.org/foo/bar", "bar") | 	_, err := NewBitbucket("https://bitbucket.org/foo/bar", "bar", nil) | ||||||
| 	assert.NotNil(t, err) | 	assert.NotNil(t, err) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -17,6 +17,8 @@ limitations under the License. | ||||||
| package notifier | package notifier | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"crypto/x509" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net" | 	"net" | ||||||
|  | @ -30,16 +32,30 @@ import ( | ||||||
| 
 | 
 | ||||||
| type requestOptFunc func(*retryablehttp.Request) | type requestOptFunc func(*retryablehttp.Request) | ||||||
| 
 | 
 | ||||||
| func postMessage(address, proxy string, payload interface{}, reqOpts ...requestOptFunc) error { | func postMessage(address, proxy string, certPool *x509.CertPool, payload interface{}, reqOpts ...requestOptFunc) error { | ||||||
| 	httpClient := retryablehttp.NewClient() | 	httpClient := retryablehttp.NewClient() | ||||||
|  | 	if certPool != nil { | ||||||
|  | 		httpClient.HTTPClient.Transport = &http.Transport{ | ||||||
|  | 			TLSClientConfig: &tls.Config{ | ||||||
|  | 				RootCAs: certPool, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	if proxy != "" { | 	if proxy != "" { | ||||||
| 		proxyURL, err := url.Parse(proxy) | 		proxyURL, err := url.Parse(proxy) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return fmt.Errorf("unable to parse proxy URL '%s', error: %w", proxy, err) | 			return fmt.Errorf("unable to parse proxy URL '%s', error: %w", proxy, err) | ||||||
| 		} | 		} | ||||||
|  | 		var tlsConfig *tls.Config | ||||||
|  | 		if certPool != nil { | ||||||
|  | 			tlsConfig = &tls.Config{ | ||||||
|  | 				RootCAs: certPool, | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 		httpClient.HTTPClient.Transport = &http.Transport{ | 		httpClient.HTTPClient.Transport = &http.Transport{ | ||||||
| 			Proxy: http.ProxyURL(proxyURL), | 			Proxy:           http.ProxyURL(proxyURL), | ||||||
|  | 			TLSClientConfig: tlsConfig, | ||||||
| 			DialContext: (&net.Dialer{ | 			DialContext: (&net.Dialer{ | ||||||
| 				Timeout:   15 * time.Second, | 				Timeout:   15 * time.Second, | ||||||
| 				KeepAlive: 30 * time.Second, | 				KeepAlive: 30 * time.Second, | ||||||
|  |  | ||||||
|  | @ -17,6 +17,7 @@ limitations under the License. | ||||||
| package notifier | package notifier | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/x509" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | @ -42,7 +43,28 @@ func Test_postMessage(t *testing.T) { | ||||||
| 		require.Equal(t, "success", payload["status"]) | 		require.Equal(t, "success", payload["status"]) | ||||||
| 	})) | 	})) | ||||||
| 	defer ts.Close() | 	defer ts.Close() | ||||||
| 	err := postMessage(ts.URL, "", map[string]string{"status": "success"}) | 	err := postMessage(ts.URL, "", nil, map[string]string{"status": "success"}) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func Test_postSelfSignedCert(t *testing.T) { | ||||||
|  | 	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		b, err := ioutil.ReadAll(r.Body) | ||||||
|  | 		require.NoError(t, err) | ||||||
|  | 
 | ||||||
|  | 		var payload = make(map[string]string) | ||||||
|  | 		err = json.Unmarshal(b, &payload) | ||||||
|  | 		require.NoError(t, err) | ||||||
|  | 
 | ||||||
|  | 		require.Equal(t, "success", payload["status"]) | ||||||
|  | 	})) | ||||||
|  | 	defer ts.Close() | ||||||
|  | 
 | ||||||
|  | 	cert, err := x509.ParseCertificate(ts.TLS.Certificates[0].Certificate[0]) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | 	certpool := x509.NewCertPool() | ||||||
|  | 	certpool.AddCert(cert) | ||||||
|  | 	err = postMessage(ts.URL, "", certpool, map[string]string{"status": "success"}) | ||||||
| 	require.NoError(t, err) | 	require.NoError(t, err) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -99,7 +99,7 @@ func (s *Discord) Post(event events.Event) error { | ||||||
| 
 | 
 | ||||||
| 	payload.Attachments = []SlackAttachment{a} | 	payload.Attachments = []SlackAttachment{a} | ||||||
| 
 | 
 | ||||||
| 	err := postMessage(s.URL, s.ProxyURL, payload) | 	err := postMessage(s.URL, s.ProxyURL, nil, payload) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("postMessage failed: %w", err) | 		return fmt.Errorf("postMessage failed: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -17,6 +17,7 @@ limitations under the License. | ||||||
| package notifier | package notifier | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/x509" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 
 | 
 | ||||||
| 	"github.com/fluxcd/notification-controller/api/v1beta1" | 	"github.com/fluxcd/notification-controller/api/v1beta1" | ||||||
|  | @ -28,15 +29,17 @@ type Factory struct { | ||||||
| 	Username string | 	Username string | ||||||
| 	Channel  string | 	Channel  string | ||||||
| 	Token    string | 	Token    string | ||||||
|  | 	CertPool *x509.CertPool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewFactory(url string, proxy string, username string, channel string, token string) *Factory { | func NewFactory(url string, proxy string, username string, channel string, token string, certPool *x509.CertPool) *Factory { | ||||||
| 	return &Factory{ | 	return &Factory{ | ||||||
| 		URL:      url, | 		URL:      url, | ||||||
| 		ProxyURL: proxy, | 		ProxyURL: proxy, | ||||||
| 		Channel:  channel, | 		Channel:  channel, | ||||||
| 		Username: username, | 		Username: username, | ||||||
| 		Token:    token, | 		Token:    token, | ||||||
|  | 		CertPool: certPool, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -49,29 +52,29 @@ func (f Factory) Notifier(provider string) (Interface, error) { | ||||||
| 	var err error | 	var err error | ||||||
| 	switch provider { | 	switch provider { | ||||||
| 	case v1beta1.GenericProvider: | 	case v1beta1.GenericProvider: | ||||||
| 		n, err = NewForwarder(f.URL, f.ProxyURL) | 		n, err = NewForwarder(f.URL, f.ProxyURL, f.CertPool) | ||||||
| 	case v1beta1.SlackProvider: | 	case v1beta1.SlackProvider: | ||||||
| 		n, err = NewSlack(f.URL, f.ProxyURL, f.Username, f.Channel) | 		n, err = NewSlack(f.URL, f.ProxyURL, f.Username, f.Channel) | ||||||
| 	case v1beta1.DiscordProvider: | 	case v1beta1.DiscordProvider: | ||||||
| 		n, err = NewDiscord(f.URL, f.ProxyURL, f.Username, f.Channel) | 		n, err = NewDiscord(f.URL, f.ProxyURL, f.Username, f.Channel) | ||||||
| 	case v1beta1.RocketProvider: | 	case v1beta1.RocketProvider: | ||||||
| 		n, err = NewRocket(f.URL, f.ProxyURL, f.Username, f.Channel) | 		n, err = NewRocket(f.URL, f.ProxyURL, f.CertPool, f.Username, f.Channel) | ||||||
| 	case v1beta1.MSTeamsProvider: | 	case v1beta1.MSTeamsProvider: | ||||||
| 		n, err = NewMSTeams(f.URL, f.ProxyURL) | 		n, err = NewMSTeams(f.URL, f.ProxyURL) | ||||||
| 	case v1beta1.GitHubProvider: | 	case v1beta1.GitHubProvider: | ||||||
| 		n, err = NewGitHub(f.URL, f.Token) | 		n, err = NewGitHub(f.URL, f.Token, f.CertPool) | ||||||
| 	case v1beta1.GitLabProvider: | 	case v1beta1.GitLabProvider: | ||||||
| 		n, err = NewGitLab(f.URL, f.Token) | 		n, err = NewGitLab(f.URL, f.Token, f.CertPool) | ||||||
| 	case v1beta1.BitbucketProvider: | 	case v1beta1.BitbucketProvider: | ||||||
| 		n, err = NewBitbucket(f.URL, f.Token) | 		n, err = NewBitbucket(f.URL, f.Token, f.CertPool) | ||||||
| 	case v1beta1.AzureDevOpsProvider: | 	case v1beta1.AzureDevOpsProvider: | ||||||
| 		n, err = NewAzureDevOps(f.URL, f.Token) | 		n, err = NewAzureDevOps(f.URL, f.Token, f.CertPool) | ||||||
| 	case v1beta1.GoogleChatProvider: | 	case v1beta1.GoogleChatProvider: | ||||||
| 		n, err = NewGoogleChat(f.URL, f.ProxyURL) | 		n, err = NewGoogleChat(f.URL, f.ProxyURL) | ||||||
| 	case v1beta1.WebexProvider: | 	case v1beta1.WebexProvider: | ||||||
| 		n, err = NewWebex(f.URL, f.ProxyURL) | 		n, err = NewWebex(f.URL, f.ProxyURL, f.CertPool) | ||||||
| 	case v1beta1.SentryProvider: | 	case v1beta1.SentryProvider: | ||||||
| 		n, err = NewSentry(f.URL) | 		n, err = NewSentry(f.CertPool, f.URL) | ||||||
| 	default: | 	default: | ||||||
| 		err = fmt.Errorf("provider %s not supported", provider) | 		err = fmt.Errorf("provider %s not supported", provider) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -17,10 +17,12 @@ limitations under the License. | ||||||
| package notifier | package notifier | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/x509" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/fluxcd/pkg/runtime/events" |  | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/fluxcd/pkg/runtime/events" | ||||||
|  | 
 | ||||||
| 	"github.com/hashicorp/go-retryablehttp" | 	"github.com/hashicorp/go-retryablehttp" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -33,9 +35,10 @@ const NotificationHeader = "gotk-component" | ||||||
| type Forwarder struct { | type Forwarder struct { | ||||||
| 	URL      string | 	URL      string | ||||||
| 	ProxyURL string | 	ProxyURL string | ||||||
|  | 	CertPool *x509.CertPool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewForwarder(hookURL string, proxyURL string) (*Forwarder, error) { | func NewForwarder(hookURL string, proxyURL string, certPool *x509.CertPool) (*Forwarder, error) { | ||||||
| 	if _, err := url.ParseRequestURI(hookURL); err != nil { | 	if _, err := url.ParseRequestURI(hookURL); err != nil { | ||||||
| 		return nil, fmt.Errorf("invalid hook URL %s: %w", hookURL, err) | 		return nil, fmt.Errorf("invalid hook URL %s: %w", hookURL, err) | ||||||
| 	} | 	} | ||||||
|  | @ -47,7 +50,7 @@ func NewForwarder(hookURL string, proxyURL string) (*Forwarder, error) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (f *Forwarder) Post(event events.Event) error { | func (f *Forwarder) Post(event events.Event) error { | ||||||
| 	err := postMessage(f.URL, f.ProxyURL, event, func(req *retryablehttp.Request) { | 	err := postMessage(f.URL, f.ProxyURL, f.CertPool, event, func(req *retryablehttp.Request) { | ||||||
| 		req.Header.Set(NotificationHeader, event.ReportingController) | 		req.Header.Set(NotificationHeader, event.ReportingController) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -18,12 +18,13 @@ package notifier | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"github.com/fluxcd/pkg/runtime/events" |  | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/http/httptest" | 	"net/http/httptest" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/fluxcd/pkg/runtime/events" | ||||||
|  | 
 | ||||||
| 	"github.com/stretchr/testify/require" | 	"github.com/stretchr/testify/require" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -41,7 +42,7 @@ func TestForwarder_Post(t *testing.T) { | ||||||
| 	})) | 	})) | ||||||
| 	defer ts.Close() | 	defer ts.Close() | ||||||
| 
 | 
 | ||||||
| 	forwarder, err := NewForwarder(ts.URL, "") | 	forwarder, err := NewForwarder(ts.URL, "", nil) | ||||||
| 	require.NoError(t, err) | 	require.NoError(t, err) | ||||||
| 
 | 
 | ||||||
| 	err = forwarder.Post(testEvent()) | 	err = forwarder.Post(testEvent()) | ||||||
|  |  | ||||||
|  | @ -18,13 +18,17 @@ package notifier | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"crypto/x509" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/fluxcd/pkg/runtime/events" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/fluxcd/pkg/runtime/events" | ||||||
|  | 
 | ||||||
| 	"github.com/google/go-github/v32/github" | 	"github.com/google/go-github/v32/github" | ||||||
| 	"golang.org/x/oauth2" | 	"golang.org/x/oauth2" | ||||||
| ) | ) | ||||||
|  | @ -35,7 +39,7 @@ type GitHub struct { | ||||||
| 	Client *github.Client | 	Client *github.Client | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewGitHub(addr string, token string) (*GitHub, error) { | func NewGitHub(addr string, token string, certPool *x509.CertPool) (*GitHub, error) { | ||||||
| 	if len(token) == 0 { | 	if len(token) == 0 { | ||||||
| 		return nil, errors.New("github token cannot be empty") | 		return nil, errors.New("github token cannot be empty") | ||||||
| 	} | 	} | ||||||
|  | @ -59,6 +63,17 @@ func NewGitHub(addr string, token string) (*GitHub, error) { | ||||||
| 	tc := oauth2.NewClient(context.Background(), ts) | 	tc := oauth2.NewClient(context.Background(), ts) | ||||||
| 	client := github.NewClient(tc) | 	client := github.NewClient(tc) | ||||||
| 	if baseUrl.Host != "github.com" { | 	if baseUrl.Host != "github.com" { | ||||||
|  | 		if certPool != nil { | ||||||
|  | 			tr := &http.Transport{ | ||||||
|  | 				TLSClientConfig: &tls.Config{ | ||||||
|  | 					RootCAs: certPool, | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  | 			hc := &http.Client{Transport: tr} | ||||||
|  | 			ctx := context.WithValue(context.Background(), oauth2.HTTPClient, hc) | ||||||
|  | 			tc = oauth2.NewClient(ctx, ts) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		client, err = github.NewEnterpriseClient(host, host, tc) | 		client, err = github.NewEnterpriseClient(host, host, tc) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, fmt.Errorf("could not create enterprise GitHub client: %v", err) | 			return nil, fmt.Errorf("could not create enterprise GitHub client: %v", err) | ||||||
|  |  | ||||||
|  | @ -24,7 +24,7 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestNewGitHubBasic(t *testing.T) { | func TestNewGitHubBasic(t *testing.T) { | ||||||
| 	g, err := NewGitHub("https://github.com/foo/bar", "foobar") | 	g, err := NewGitHub("https://github.com/foo/bar", "foobar", nil) | ||||||
| 	assert.Nil(t, err) | 	assert.Nil(t, err) | ||||||
| 	assert.Equal(t, g.Owner, "foo") | 	assert.Equal(t, g.Owner, "foo") | ||||||
| 	assert.Equal(t, g.Repo, "bar") | 	assert.Equal(t, g.Repo, "bar") | ||||||
|  | @ -32,7 +32,7 @@ func TestNewGitHubBasic(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestNewEmterpriseGitHubBasic(t *testing.T) { | func TestNewEmterpriseGitHubBasic(t *testing.T) { | ||||||
| 	g, err := NewGitHub("https://foobar.com/foo/bar", "foobar") | 	g, err := NewGitHub("https://foobar.com/foo/bar", "foobar", nil) | ||||||
| 	assert.Nil(t, err) | 	assert.Nil(t, err) | ||||||
| 	assert.Equal(t, g.Owner, "foo") | 	assert.Equal(t, g.Owner, "foo") | ||||||
| 	assert.Equal(t, g.Repo, "bar") | 	assert.Equal(t, g.Repo, "bar") | ||||||
|  | @ -40,12 +40,12 @@ func TestNewEmterpriseGitHubBasic(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestNewGitHubInvalidUrl(t *testing.T) { | func TestNewGitHubInvalidUrl(t *testing.T) { | ||||||
| 	_, err := NewGitHub("https://github.com/foo/bar/baz", "foobar") | 	_, err := NewGitHub("https://github.com/foo/bar/baz", "foobar", nil) | ||||||
| 	assert.NotNil(t, err) | 	assert.NotNil(t, err) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestNewGitHubEmptyToken(t *testing.T) { | func TestNewGitHubEmptyToken(t *testing.T) { | ||||||
| 	_, err := NewGitHub("https://github.com/foo/bar", "") | 	_, err := NewGitHub("https://github.com/foo/bar", "", nil) | ||||||
| 	assert.NotNil(t, err) | 	assert.NotNil(t, err) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -17,7 +17,10 @@ limitations under the License. | ||||||
| package notifier | package notifier | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"crypto/x509" | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"net/http" | ||||||
| 
 | 
 | ||||||
| 	"github.com/fluxcd/pkg/runtime/events" | 	"github.com/fluxcd/pkg/runtime/events" | ||||||
| 	"github.com/xanzy/go-gitlab" | 	"github.com/xanzy/go-gitlab" | ||||||
|  | @ -28,7 +31,7 @@ type GitLab struct { | ||||||
| 	Client *gitlab.Client | 	Client *gitlab.Client | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewGitLab(addr string, token string) (*GitLab, error) { | func NewGitLab(addr string, token string, certPool *x509.CertPool) (*GitLab, error) { | ||||||
| 	if len(token) == 0 { | 	if len(token) == 0 { | ||||||
| 		return nil, errors.New("gitlab token cannot be empty") | 		return nil, errors.New("gitlab token cannot be empty") | ||||||
| 	} | 	} | ||||||
|  | @ -38,8 +41,17 @@ func NewGitLab(addr string, token string) (*GitLab, error) { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	opt := gitlab.WithBaseURL(host) | 	opts := []gitlab.ClientOptionFunc{gitlab.WithBaseURL(host)} | ||||||
| 	client, err := gitlab.NewClient(token, opt) | 	if certPool != nil { | ||||||
|  | 		tr := &http.Transport{ | ||||||
|  | 			TLSClientConfig: &tls.Config{ | ||||||
|  | 				RootCAs: certPool, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		hc := &http.Client{Transport: tr} | ||||||
|  | 		opts = append(opts, gitlab.WithHTTPClient(hc)) | ||||||
|  | 	} | ||||||
|  | 	client, err := gitlab.NewClient(token, opts...) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -23,25 +23,25 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestNewGitLabBasic(t *testing.T) { | func TestNewGitLabBasic(t *testing.T) { | ||||||
| 	g, err := NewGitLab("https://gitlab.com/foo/bar", "foobar") | 	g, err := NewGitLab("https://gitlab.com/foo/bar", "foobar", nil) | ||||||
| 	assert.Nil(t, err) | 	assert.Nil(t, err) | ||||||
| 	assert.Equal(t, g.Id, "foo/bar") | 	assert.Equal(t, g.Id, "foo/bar") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestNewGitLabSubgroups(t *testing.T) { | func TestNewGitLabSubgroups(t *testing.T) { | ||||||
| 	g, err := NewGitLab("https://gitlab.com/foo/bar/baz", "foobar") | 	g, err := NewGitLab("https://gitlab.com/foo/bar/baz", "foobar", nil) | ||||||
| 	assert.Nil(t, err) | 	assert.Nil(t, err) | ||||||
| 	assert.Equal(t, g.Id, "foo/bar/baz") | 	assert.Equal(t, g.Id, "foo/bar/baz") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestNewGitLabSelfHosted(t *testing.T) { | func TestNewGitLabSelfHosted(t *testing.T) { | ||||||
| 	g, err := NewGitLab("https://example.com/foo/bar", "foo:bar") | 	g, err := NewGitLab("https://example.com/foo/bar", "foo:bar", nil) | ||||||
| 	assert.Nil(t, err) | 	assert.Nil(t, err) | ||||||
| 	assert.Equal(t, g.Id, "foo/bar") | 	assert.Equal(t, g.Id, "foo/bar") | ||||||
| 	assert.Equal(t, g.Client.BaseURL().Host, "example.com") | 	assert.Equal(t, g.Client.BaseURL().Host, "example.com") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestNewGitLabEmptyToken(t *testing.T) { | func TestNewGitLabEmptyToken(t *testing.T) { | ||||||
| 	_, err := NewGitLab("https://gitlab.com/foo/bar", "") | 	_, err := NewGitLab("https://gitlab.com/foo/bar", "", nil) | ||||||
| 	assert.NotNil(t, err) | 	assert.NotNil(t, err) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -141,7 +141,7 @@ func (s *GoogleChat) Post(event events.Event) error { | ||||||
| 		Cards: []GoogleChatCard{card}, | 		Cards: []GoogleChatCard{card}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err := postMessage(s.URL, s.ProxyURL, payload) | 	err := postMessage(s.URL, s.ProxyURL, nil, payload) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("postMessage failed: %w", err) | 		return fmt.Errorf("postMessage failed: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -17,6 +17,7 @@ limitations under the License. | ||||||
| package notifier | package notifier | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/x509" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  | @ -31,10 +32,11 @@ type Rocket struct { | ||||||
| 	ProxyURL string | 	ProxyURL string | ||||||
| 	Username string | 	Username string | ||||||
| 	Channel  string | 	Channel  string | ||||||
|  | 	CertPool *x509.CertPool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewRocket validates the Rocket URL and returns a Rocket object
 | // NewRocket validates the Rocket URL and returns a Rocket object
 | ||||||
| func NewRocket(hookURL string, proxyURL string, username string, channel string) (*Rocket, error) { | func NewRocket(hookURL string, proxyURL string, certPool *x509.CertPool, username string, channel string) (*Rocket, error) { | ||||||
| 	_, err := url.ParseRequestURI(hookURL) | 	_, err := url.ParseRequestURI(hookURL) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("invalid Rocket hook URL %s", hookURL) | 		return nil, fmt.Errorf("invalid Rocket hook URL %s", hookURL) | ||||||
|  | @ -53,6 +55,7 @@ func NewRocket(hookURL string, proxyURL string, username string, channel string) | ||||||
| 		URL:      hookURL, | 		URL:      hookURL, | ||||||
| 		ProxyURL: proxyURL, | 		ProxyURL: proxyURL, | ||||||
| 		Username: username, | 		Username: username, | ||||||
|  | 		CertPool: certPool, | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -88,7 +91,7 @@ func (s *Rocket) Post(event events.Event) error { | ||||||
| 
 | 
 | ||||||
| 	payload.Attachments = []SlackAttachment{a} | 	payload.Attachments = []SlackAttachment{a} | ||||||
| 
 | 
 | ||||||
| 	err := postMessage(s.URL, s.ProxyURL, payload) | 	err := postMessage(s.URL, s.ProxyURL, s.CertPool, payload) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("postMessage failed: %w", err) | 		return fmt.Errorf("postMessage failed: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -39,7 +39,7 @@ func TestRocket_Post(t *testing.T) { | ||||||
| 	})) | 	})) | ||||||
| 	defer ts.Close() | 	defer ts.Close() | ||||||
| 
 | 
 | ||||||
| 	rocket, err := NewRocket(ts.URL, "", "test", "test") | 	rocket, err := NewRocket(ts.URL, "", nil, "test", "test") | ||||||
| 	require.NoError(t, err) | 	require.NoError(t, err) | ||||||
| 
 | 
 | ||||||
| 	err = rocket.Post(testEvent()) | 	err = rocket.Post(testEvent()) | ||||||
|  |  | ||||||
|  | @ -17,7 +17,11 @@ limitations under the License. | ||||||
| package notifier | package notifier | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"crypto/x509" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
|  | 
 | ||||||
| 	"github.com/fluxcd/pkg/runtime/events" | 	"github.com/fluxcd/pkg/runtime/events" | ||||||
| 	"github.com/getsentry/sentry-go" | 	"github.com/getsentry/sentry-go" | ||||||
| ) | ) | ||||||
|  | @ -28,9 +32,18 @@ type Sentry struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewSentry creates a Sentry client from the provided Data Source Name (DSN)
 | // NewSentry creates a Sentry client from the provided Data Source Name (DSN)
 | ||||||
| func NewSentry(dsn string) (*Sentry, error) { | func NewSentry(certPool *x509.CertPool, dsn string) (*Sentry, error) { | ||||||
|  | 	var tr *http.Transport | ||||||
|  | 	if certPool != nil { | ||||||
|  | 		tr = &http.Transport{ | ||||||
|  | 			TLSClientConfig: &tls.Config{ | ||||||
|  | 				RootCAs: certPool, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	client, err := sentry.NewClient(sentry.ClientOptions{ | 	client, err := sentry.NewClient(sentry.ClientOptions{ | ||||||
| 		Dsn: dsn, | 		Dsn:           dsn, | ||||||
|  | 		HTTPTransport: tr, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  |  | ||||||
|  | @ -17,19 +17,20 @@ limitations under the License. | ||||||
| package notifier | package notifier | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
| 	"github.com/fluxcd/pkg/runtime/events" | 	"github.com/fluxcd/pkg/runtime/events" | ||||||
| 	"github.com/getsentry/sentry-go" | 	"github.com/getsentry/sentry-go" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"testing" |  | ||||||
| 	"time" |  | ||||||
| 
 | 
 | ||||||
| 	"github.com/stretchr/testify/require" | 	"github.com/stretchr/testify/require" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestNewSentry(t *testing.T) { | func TestNewSentry(t *testing.T) { | ||||||
| 	s, err := NewSentry("https://test@localhost/1") | 	s, err := NewSentry(nil, "https://test@localhost/1") | ||||||
| 	require.NoError(t, err) | 	require.NoError(t, err) | ||||||
| 	assert.Equal(t, s.Client.Options().Dsn, "https://test@localhost/1") | 	assert.Equal(t, s.Client.Options().Dsn, "https://test@localhost/1") | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -112,7 +112,7 @@ func (s *Slack) Post(event events.Event) error { | ||||||
| 
 | 
 | ||||||
| 	payload.Attachments = []SlackAttachment{a} | 	payload.Attachments = []SlackAttachment{a} | ||||||
| 
 | 
 | ||||||
| 	err := postMessage(s.URL, s.ProxyURL, payload) | 	err := postMessage(s.URL, s.ProxyURL, nil, payload) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("postMessage failed: %w", err) | 		return fmt.Errorf("postMessage failed: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -98,7 +98,7 @@ func (s *MSTeams) Post(event events.Event) error { | ||||||
| 		payload.ThemeColor = "FF0000" | 		payload.ThemeColor = "FF0000" | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err := postMessage(s.URL, s.ProxyURL, payload) | 	err := postMessage(s.URL, s.ProxyURL, nil, payload) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("postMessage failed: %w", err) | 		return fmt.Errorf("postMessage failed: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -17,6 +17,7 @@ limitations under the License. | ||||||
| package notifier | package notifier | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/x509" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | @ -28,6 +29,7 @@ import ( | ||||||
| type Webex struct { | type Webex struct { | ||||||
| 	URL      string | 	URL      string | ||||||
| 	ProxyURL string | 	ProxyURL string | ||||||
|  | 	CertPool *x509.CertPool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // WebexPayload holds the message text
 | // WebexPayload holds the message text
 | ||||||
|  | @ -37,7 +39,7 @@ type WebexPayload struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewWebex validates the Webex URL and returns a Webex object
 | // NewWebex validates the Webex URL and returns a Webex object
 | ||||||
| func NewWebex(hookURL, proxyURL string) (*Webex, error) { | func NewWebex(hookURL, proxyURL string, certPool *x509.CertPool) (*Webex, error) { | ||||||
| 	_, err := url.ParseRequestURI(hookURL) | 	_, err := url.ParseRequestURI(hookURL) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("invalid Webex hook URL %s", hookURL) | 		return nil, fmt.Errorf("invalid Webex hook URL %s", hookURL) | ||||||
|  | @ -71,7 +73,7 @@ func (s *Webex) Post(event events.Event) error { | ||||||
| 		Markdown: markdown, | 		Markdown: markdown, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := postMessage(s.URL, s.ProxyURL, payload); err != nil { | 	if err := postMessage(s.URL, s.ProxyURL, s.CertPool, payload); err != nil { | ||||||
| 		return fmt.Errorf("postMessage failed: %w", err) | 		return fmt.Errorf("postMessage failed: %w", err) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
|  |  | ||||||
|  | @ -39,7 +39,7 @@ func TestWebex_Post(t *testing.T) { | ||||||
| 	})) | 	})) | ||||||
| 	defer ts.Close() | 	defer ts.Close() | ||||||
| 
 | 
 | ||||||
| 	webex, err := NewWebex(ts.URL, "") | 	webex, err := NewWebex(ts.URL, "", nil) | ||||||
| 	require.NoError(t, err) | 	require.NoError(t, err) | ||||||
| 
 | 
 | ||||||
| 	err = webex.Post(testEvent()) | 	err = webex.Post(testEvent()) | ||||||
|  | @ -47,7 +47,7 @@ func TestWebex_Post(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestWebex_PostUpdate(t *testing.T) { | func TestWebex_PostUpdate(t *testing.T) { | ||||||
| 	webex, err := NewWebex("http://localhost", "") | 	webex, err := NewWebex("http://localhost", "", nil) | ||||||
| 	require.NoError(t, err) | 	require.NoError(t, err) | ||||||
| 
 | 
 | ||||||
| 	event := testEvent() | 	event := testEvent() | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ package server | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"crypto/x509" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
|  | @ -156,6 +157,40 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | 			var certPool *x509.CertPool | ||||||
|  | 			if provider.Spec.CertSecretRef != nil { | ||||||
|  | 				var secret corev1.Secret | ||||||
|  | 				secretName := types.NamespacedName{Namespace: alert.Namespace, Name: provider.Spec.CertSecretRef.Name} | ||||||
|  | 
 | ||||||
|  | 				err = s.kubeClient.Get(ctx, secretName, &secret) | ||||||
|  | 				if err != nil { | ||||||
|  | 					s.logger.Error(err, "failed to read secret", | ||||||
|  | 						"reconciler kind", v1beta1.ProviderKind, | ||||||
|  | 						"name", providerName.Name, | ||||||
|  | 						"namespace", providerName.Namespace) | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				caFile, ok := secret.Data["caFile"] | ||||||
|  | 				if !ok { | ||||||
|  | 					s.logger.Error(err, "failed to read secret key caFile", | ||||||
|  | 						"reconciler kind", v1beta1.ProviderKind, | ||||||
|  | 						"name", providerName.Name, | ||||||
|  | 						"namespace", providerName.Namespace) | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				certPool = x509.NewCertPool() | ||||||
|  | 				ok = certPool.AppendCertsFromPEM(caFile) | ||||||
|  | 				if !ok { | ||||||
|  | 					s.logger.Error(err, "could not append to cert pool", | ||||||
|  | 						"reconciler kind", v1beta1.ProviderKind, | ||||||
|  | 						"name", providerName.Name, | ||||||
|  | 						"namespace", providerName.Namespace) | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
| 			if webhook == "" { | 			if webhook == "" { | ||||||
| 				s.logger.Error(nil, "provider has no address", | 				s.logger.Error(nil, "provider has no address", | ||||||
| 					"reconciler kind", v1beta1.ProviderKind, | 					"reconciler kind", v1beta1.ProviderKind, | ||||||
|  | @ -164,7 +199,7 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request) | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			factory := notifier.NewFactory(webhook, provider.Spec.Proxy, provider.Spec.Username, provider.Spec.Channel, token) | 			factory := notifier.NewFactory(webhook, provider.Spec.Proxy, provider.Spec.Username, provider.Spec.Channel, token, certPool) | ||||||
| 			sender, err := factory.Notifier(provider.Spec.Type) | 			sender, err := factory.Notifier(provider.Spec.Type) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				s.logger.Error(err, "failed to initialise provider", | 				s.logger.Error(err, "failed to initialise provider", | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue