Skip to content

Commit

Permalink
Merge pull request #184 from fluxcd/feature/self-signed-certs
Browse files Browse the repository at this point in the history
Add self signed cert to provider
  • Loading branch information
stefanprodan authored Apr 21, 2021
2 parents f049e4b + a98961f commit 490c955
Show file tree
Hide file tree
Showing 30 changed files with 291 additions and 58 deletions.
5 changes: 5 additions & 0 deletions api/v1beta1/provider_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ type ProviderSpec struct {
// using "address" as data key
// +optional
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 (
Expand Down
5 changes: 5 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions config/crd/bases/notification.toolkit.fluxcd.io_providers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ spec:
description: HTTP/S webhook address of this provider
pattern: ^(http|https)://
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:
description: Alert channel for this provider
type: string
Expand Down
24 changes: 23 additions & 1 deletion controllers/provider_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package controllers

import (
"context"
"crypto/x509"
"fmt"
"time"

Expand Down Expand Up @@ -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`")
}

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 {
return fmt.Errorf("failed to initialise provider, error: %w", err)
}
Expand Down
30 changes: 30 additions & 0 deletions docs/api/notification.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,21 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
using &ldquo;address&rdquo; as data key</p>
</td>
</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>
</td>
</tr>
Expand Down Expand Up @@ -761,6 +776,21 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
using &ldquo;address&rdquo; as data key</p>
</td>
</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>
</table>
</div>
Expand Down
13 changes: 13 additions & 0 deletions docs/spec/v1beta1/provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,16 @@ The body of the request looks like this:
```

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
```
12 changes: 10 additions & 2 deletions internal/notifier/azure_devops.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ package notifier

import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"github.com/fluxcd/pkg/runtime/events"
"strings"
"time"

"github.com/fluxcd/pkg/runtime/events"

"github.com/microsoft/azure-devops-go-api/azuredevops"
"github.com/microsoft/azure-devops-go-api/azuredevops/git"
)
Expand All @@ -38,7 +41,7 @@ type AzureDevOps struct {
}

// 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 {
return nil, errors.New("azure devops token cannot be empty")
}
Expand All @@ -58,6 +61,11 @@ func NewAzureDevOps(addr string, token string) (*AzureDevOps, error) {

orgURL := fmt.Sprintf("%v/%v", host, org)
connection := azuredevops.NewPatConnection(orgURL, token)
if certPool != nil {
connection.TlsConfig = &tls.Config{
RootCAs: certPool,
}
}
client := connection.GetClientByUrl(orgURL)
gitClient := &git.ClientImpl{
Client: *client,
Expand Down
6 changes: 3 additions & 3 deletions internal/notifier/azure_devops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@ import (
)

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.Equal(t, a.Project, "bar")
assert.Equal(t, a.Repo, "baz")
}

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)
}

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)
}

Expand Down
18 changes: 16 additions & 2 deletions internal/notifier/bitbucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ limitations under the License.
package notifier

import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net/http"
"strings"

"github.com/fluxcd/pkg/runtime/events"
Expand All @@ -33,7 +36,7 @@ type Bitbucket struct {
}

// 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 {
return nil, errors.New("bitbucket token cannot be empty")
}
Expand All @@ -57,10 +60,21 @@ func NewBitbucket(addr string, token string) (*Bitbucket, error) {
owner := comp[0]
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{
Owner: owner,
Repo: repo,
Client: bitbucket.NewBasicAuth(username, password),
Client: client,
}, nil
}

Expand Down
6 changes: 3 additions & 3 deletions internal/notifier/bitbucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@ import (
)

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.Equal(t, b.Owner, "foo")
assert.Equal(t, b.Repo, "bar")
}

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)
}

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)
}
20 changes: 18 additions & 2 deletions internal/notifier/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package notifier

import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"net"
Expand All @@ -30,16 +32,30 @@ import (

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()
if certPool != nil {
httpClient.HTTPClient.Transport = &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: certPool,
},
}
}

if proxy != "" {
proxyURL, err := url.Parse(proxy)
if err != nil {
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{
Proxy: http.ProxyURL(proxyURL),
Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: tlsConfig,
DialContext: (&net.Dialer{
Timeout: 15 * time.Second,
KeepAlive: 30 * time.Second,
Expand Down
24 changes: 23 additions & 1 deletion internal/notifier/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package notifier

import (
"crypto/x509"
"encoding/json"
"io/ioutil"
"net/http"
Expand All @@ -42,7 +43,28 @@ func Test_postMessage(t *testing.T) {
require.Equal(t, "success", payload["status"])
}))
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)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/notifier/discord.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (s *Discord) Post(event events.Event) error {

payload.Attachments = []SlackAttachment{a}

err := postMessage(s.URL, s.ProxyURL, payload)
err := postMessage(s.URL, s.ProxyURL, nil, payload)
if err != nil {
return fmt.Errorf("postMessage failed: %w", err)
}
Expand Down
Loading

0 comments on commit 490c955

Please sign in to comment.