Skip to content
This repository has been archived by the owner on Feb 24, 2024. It is now read-only.

Commit

Permalink
Merge pull request #26 from fluxcd/nexus
Browse files Browse the repository at this point in the history
Nexus Repository Manager support
  • Loading branch information
squaremo authored May 3, 2020
2 parents 2557181 + 2376c30 commit 5a6d833
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 15 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ exposing to the internet, because
- `GitLab` push events
- `Bitbucket` push events
- `GoogleContainerRegistry` image push events via pubsub
- `Nexus` image push events

## How to use it

Expand Down
1 change: 1 addition & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type Auth struct {

type Endpoint struct {
Source string `json:"source"`
RegistryHost string `json:"registryHost,omitempty"`
KeyPath string `json:"keyPath"`
Authentication *Auth `json:"authentication,omitempty"`
}
Expand Down
4 changes: 0 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ module github.com/fluxcd/flux-recv
go 1.13

require (
github.com/docker/docker v1.13.1 // indirect
github.com/docker/engine v1.13.1
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/fluxcd/flux v1.15.0
github.com/ghodss/yaml v1.0.0
github.com/google/go-github/v28 v28.1.1
Expand Down
18 changes: 7 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
github.com/2opremio/distribution v0.0.0-20190419185413-6c9727e5e5de h1:BNSXHiWNaMNhx2g1bbIubySvhdKyNF+0bepwZVa1Q6k=
github.com/2opremio/distribution v0.0.0-20190419185413-6c9727e5e5de/go.mod h1:QHT6cqKT8fLkQMioAxx43yuZxuzwV655sKt60H8N17Q=
github.com/Azure/go-autorest v11.7.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
Expand All @@ -14,6 +12,7 @@ github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0
github.com/Masterminds/sprig v0.0.0-20190301161902-9f8fceff796f/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
Expand All @@ -34,17 +33,9 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/engine v1.13.1 h1:Cks33UT9YBW5Xyc3MtGDq2IPgqfJtJ+qkFaxc2b0Euc=
github.com/docker/engine v1.13.1/go.mod h1:3CPr2caMgTHxxIAZgEMd3uLYPDlRvPqCpyeRf6ncPcY=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/docker/spdystream v0.0.0-20170912183627-bc6354cbbc29/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
Expand All @@ -71,6 +62,7 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
Expand All @@ -95,8 +87,8 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo=
github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
Expand Down Expand Up @@ -147,8 +139,10 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
Expand Down Expand Up @@ -300,6 +294,7 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdO
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20180810153555-6e3c4e7365dd/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
Expand Down Expand Up @@ -334,6 +329,7 @@ google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLD
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
Expand Down
84 changes: 84 additions & 0 deletions nexus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package main

import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"

fluxapi "github.com/fluxcd/flux/pkg/api"
)

const Nexus = "Nexus"

func init() {
Sources[Nexus] = handleNexus
}

func handleNexus(s fluxapi.Server, key []byte, w http.ResponseWriter, r *http.Request, e Endpoint) {
if webhookID := r.Header.Get("X-Nexus-Webhook-Id"); webhookID != "rm:repository:component" {
http.Error(w, "Unsupported webhook ID", http.StatusBadRequest)
log(Nexus, "unsupported webhook ID:", webhookID)
return
}

signature := r.Header.Get("X-Nexus-Webhook-Signature")
if len(signature) == 0 {
http.Error(w, "Signature is missing from header", http.StatusUnauthorized)
log(Nexus, "missing X-Nexus-Webhook-Signature header")
return
}

b, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Cannot read payload", http.StatusBadRequest)
log(Nexus, fmt.Errorf("could not read payload: %v", err))
return
}

if !verifyHmacSignature(key, signature, b) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
log(Nexus, "invalid X-Nexus-Webhook-Signature")
return
}

type payload struct {
Action string `json:"action"`
Component struct {
Format string `json:"format"`
Name string `json:"name"`
} `json:"component"`
}

var p payload
if err := json.Unmarshal(b, &p); err != nil {
http.Error(w, "Cannot decode webhook payload", http.StatusBadRequest)
log(Nexus, err.Error())
return
}

if p.Component.Format != "docker" || p.Action != "CREATED" {
http.Error(w, "Ignoring component format", http.StatusBadRequest)
log(Nexus, "ignoring action:", p.Action, "for asset format:", p.Component.Format)
return
}

// The request Nexus makes contains no information about the
// hostname of the Docker registry.
img := p.Component.Name
if e.RegistryHost != "" {
img = strings.TrimRight(e.RegistryHost, "/") + "/" + img
}
doImageNotify(s, w, r, img)
}

func verifyHmacSignature(key []byte, signature string, payload []byte) bool {
mac := hmac.New(sha1.New, key)
_, _ = mac.Write(payload)
expectedMAC := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expectedMAC))
}
1 change: 1 addition & 0 deletions sources.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func HandlerFromEndpoint(baseDir, apiUrl string, ep Endpoint) (string, http.Hand

sha := sha256.New()
sha.Write(key)
sha.Write([]byte(ep.RegistryHost))
digest := fmt.Sprintf("%x", sha.Sum(nil))

apiClient := fluxclient.New(http.DefaultClient, fluxhttp.NewAPIRouter(), apiUrl, fluxclient.Token(""))
Expand Down
41 changes: 41 additions & 0 deletions sources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,47 @@ func Test_Harbor(t *testing.T) {
assert.Equal(t, 401, res.StatusCode)
}

const expectedNexus = `{"Kind":"image","Source":{"Name":{"Domain":"container.example.com","Image":"app1/alpine"}}}`

func Test_Nexus(t *testing.T) {
var called bool
downstream := newDownstream(t, expectedNexus, &called)
defer downstream.Close()

endpoint := Endpoint{Source: Nexus, KeyPath: "nexus_key", RegistryHost: "container.example.com"}
fp, handler, err := HandlerFromEndpoint("test/fixtures", downstream.URL, endpoint)
assert.NoError(t, err)

hookServer := httptest.NewTLSServer(handler)
defer hookServer.Close()

payload := loadFixture(t, "nexus_payload")

c := hookServer.Client()
req, err := http.NewRequest("POST", hookServer.URL+"/hook/"+fp, bytes.NewReader(payload))
assert.NoError(t, err)
req.Header.Set("X-Nexus-Webhook-Id", "rm:repository:component")
req.Header.Set("X-Nexus-Webhook-Delivery", "bd9e6aef-0e27-4570-980d-f639c49ab5ed")
req.Header.Set("X-Nexus-Webhook-Signature", "d06eea380d4631e8c1180b689d10d9ba83ab68f6")

res, err := c.Do(req)
assert.NoError(t, err)
assert.True(t, called)
assert.Equal(t, 200, res.StatusCode)

// check that bogus signature is rejected
called = false
req, err = http.NewRequest("POST", hookServer.URL+"/hook/"+fp, bytes.NewReader(payload))
assert.NoError(t, err)
req.Header.Set("X-Nexus-Webhook-Id", "rm:repository:component")
req.Header.Set("X-Nexus-Webhook-Delivery", "bd9e6aef-0e27-4570-980d-f639c49ab5ed")
req.Header.Set("X-Nexus-Webhook-Signature", "BOGUS")
res, err = c.Do(req)
assert.NoError(t, err)
assert.False(t, called)
assert.Equal(t, 401, res.StatusCode)
}

const expectedBitbucketCloud = `{"Kind":"git","Source":{"URL":"[email protected]:mbridgen/dummy.git","Branch":"master"}}`

func Test_BitbucketCloud(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/nexus_key
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
gk0qp4u98xy0vntojv81i8zcjr4zw7xd6c4ugzad
1 change: 1 addition & 0 deletions test/fixtures/nexus_payload
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"timestamp":"2020-04-05T21:49:44.219+0000","nodeId":"8C4F3E91-CD46AFE0-61B8403D-94526884-19AC4E06","initiator":"admin/127.0.0.1","repositoryName":"app1","action":"CREATED","component":{"id":"c770169c0b2e3ed8357368b43d4da3d8","componentId":"YXBwMTpjNzcwMTY5YzBiMmUzZWQ4MzU3MzY4YjQzZDRkYTNkOA","format":"docker","name":"app1/alpine","version":"3.4"}}

0 comments on commit 5a6d833

Please sign in to comment.