Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reverse proxy authentication to Woodpeck and Gitea #176

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions cmd/drone-server/flags.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2019 Laszlo Fogas
// Copyright 2021 Informatyka Boguslawski sp. z o.o. sp.k., http://www.ib.pl/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -11,6 +12,8 @@
// 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.
//
// This file has been modified by Informatyka Boguslawski sp. z o.o. sp.k.

package main

Expand All @@ -31,6 +34,11 @@ var flags = []cli.Flag{
Name: "server-host",
Usage: "server fully qualified url (<scheme>://<host>)",
},
cli.StringFlag{
EnvVar: "WOODPECKER_HOST_INTERNAL",
Name: "server-host-internal",
Usage: "server internal fully qualified url (<scheme>://<host>)",
},
cli.StringFlag{
EnvVar: "DRONE_SERVER_ADDR,WOODPECKER_SERVER_ADDR",
Name: "server-addr",
Expand Down Expand Up @@ -500,4 +508,14 @@ var flags = []cli.Flag{
Name: "keepalive-min-time",
Usage: "server-side enforcement policy on the minimum amount of time a client should wait before sending a keepalive ping.",
},
cli.BoolFlag{
EnvVar: "WOODPECKER_GITEA_REV_PROXY_AUTH",
Name: "gitea-rev-proxy-auth",
Usage: "enable gitea authentication using HTTP header specified in WOODPECKER_GITEA_REV_PROXY_AUTH_HEADER",
},
cli.StringFlag{
EnvVar: "WOODPECKER_GITEA_REV_PROXY_AUTH_HEADER",
Name: "gitea-rev-proxy-auth-header",
Usage: "HTTP header with gitea authenticated user login",
},
}
16 changes: 16 additions & 0 deletions cmd/drone-server/server.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2018 Drone.IO Inc.
// Copyright 2021 Informatyka Boguslawski sp. z o.o. sp.k., http://www.ib.pl/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -11,6 +12,8 @@
// 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.
//
// This file has been modified by Informatyka Boguslawski sp. z o.o. sp.k.

package main

Expand Down Expand Up @@ -75,6 +78,18 @@ func server(c *cli.Context) error {
)
}

if c.String("server-host-internal") != "" && !strings.Contains(c.String("server-host-internal"), "://") {
logrus.Fatalln(
"WOODPECKER_HOST_INTERNAL must be <scheme>://<hostname> format",
)
}

if c.String("server-host-internal") != "" && strings.HasSuffix(c.String("server-host-internal"), "/") {
logrus.Fatalln(
"WOODPECKER_HOST_INTERNAL must not have trailing slash",
)
}

remote_, err := SetupRemote(c)
if err != nil {
logrus.Fatal(err)
Expand Down Expand Up @@ -225,6 +240,7 @@ func setupEvilGlobals(c *cli.Context, v store.Store, r remote.Remote) {
droneserver.Config.Server.Key = c.String("server-key")
droneserver.Config.Server.Pass = c.String("agent-secret")
droneserver.Config.Server.Host = strings.TrimRight(c.String("server-host"), "/")
droneserver.Config.Server.HostInternal = strings.TrimRight(c.String("server-host-internal"), "/")
droneserver.Config.Server.Port = c.String("server-addr")
droneserver.Config.Server.RepoConfig = c.String("repo-config")
droneserver.Config.Server.SessionExpires = c.Duration("session-expires")
Expand Down
17 changes: 11 additions & 6 deletions cmd/drone-server/setup.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2018 Drone.IO Inc.
// Copyright 2021 Informatyka Boguslawski sp. z o.o. sp.k., http://www.ib.pl/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -11,6 +12,8 @@
// 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.
//
// This file has been modified by Informatyka Boguslawski sp. z o.o. sp.k.

package main

Expand Down Expand Up @@ -119,12 +122,14 @@ func setupGogs(c *cli.Context) (remote.Remote, error) {
func setupGitea(c *cli.Context) (remote.Remote, error) {
if !c.IsSet("gitea-client") {
return gitea.New(gitea.Opts{
URL: c.String("gitea-server"),
Context: c.String("gitea-context"),
Username: c.String("gitea-git-username"),
Password: c.String("gitea-git-password"),
PrivateMode: c.Bool("gitea-private-mode"),
SkipVerify: c.Bool("gitea-skip-verify"),
URL: c.String("gitea-server"),
Context: c.String("gitea-context"),
Username: c.String("gitea-git-username"),
Password: c.String("gitea-git-password"),
PrivateMode: c.Bool("gitea-private-mode"),
SkipVerify: c.Bool("gitea-skip-verify"),
RevProxyAuth: c.Bool("gitea-rev-proxy-auth"),
RevProxyAuthHeader: c.String("gitea-rev-proxy-auth-header"),
})
}
return gitea.NewOauth(gitea.Opts{
Expand Down
92 changes: 66 additions & 26 deletions remote/gitea/gitea.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,29 @@ import (

// Opts defines configuration options.
type Opts struct {
URL string // Gitea server url.
Context string // Context to display in status check
Client string // OAuth2 Client ID
Secret string // OAuth2 Client Secret
Username string // Optional machine account username.
Password string // Optional machine account password.
PrivateMode bool // Gitea is running in private mode.
SkipVerify bool // Skip ssl verification.
URL string // Gitea server url.
Context string // Context to display in status check
Client string // OAuth2 Client ID
Secret string // OAuth2 Client Secret
Username string // Optional machine account username.
Password string // Optional machine account password.
PrivateMode bool // Gitea is running in private mode.
SkipVerify bool // Skip ssl verification.
RevProxyAuth bool // Enable reverse proxy authentication using RevProxyAuthHeader.
RevProxyAuthHeader string // Name of HTTP header with username for reverse proxy authentication.
}

type client struct {
URL string
Context string
Machine string
Username string
Password string
PrivateMode bool
SkipVerify bool
URL string
Context string
Machine string
Username string
Password string
PrivateMode bool
SkipVerify bool
RevProxyAuth bool
RevProxyAuthHeader string
RevProxyAuthHeaderValue string
}

const (
Expand Down Expand Up @@ -117,26 +122,36 @@ func New(opts Opts) (remote.Remote, error) {
url.Host = host
}
return &client{
URL: opts.URL,
Context: opts.Context,
Machine: url.Host,
Username: opts.Username,
Password: opts.Password,
PrivateMode: opts.PrivateMode,
SkipVerify: opts.SkipVerify,
URL: opts.URL,
Context: opts.Context,
Machine: url.Host,
Username: opts.Username,
Password: opts.Password,
PrivateMode: opts.PrivateMode,
SkipVerify: opts.SkipVerify,
RevProxyAuth: opts.RevProxyAuth,
RevProxyAuthHeader: opts.RevProxyAuthHeader,
}, nil
}

// Login authenticates an account with Gitea using basic authentication. The
// Gitea account details are returned when the user is successfully authenticated.
func (c *client) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) {
var (

var username string
var password string

if c.RevProxyAuth {
username = req.Header.Get(c.RevProxyAuthHeader)
c.RevProxyAuthHeaderValue = username
password = ""
} else {
username = req.FormValue("username")
password = req.FormValue("password")
)
}

// if the username or password is empty we re-direct to the login screen.
if len(username) == 0 || len(password) == 0 {
// Redirect to the login screen if user credentials are incomplete.
if len(username) == 0 || (!c.RevProxyAuth && len(password) == 0) {
http.Redirect(res, req, "/login/form", http.StatusSeeOther)
return nil, nil
}
Expand Down Expand Up @@ -394,6 +409,22 @@ func (c *client) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
return parseHook(r)
}

// authTransport forwards authentication HTTP header to gitea.
type authTransport struct {
headerName string
headerValue string
underlyingTransport http.RoundTripper
}

func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Add(t.headerName, t.headerValue)
if t.underlyingTransport != nil {
return t.underlyingTransport.RoundTrip(req)
} else {
return http.DefaultTransport.RoundTrip(req)
}
}

// helper function to return the Gitea client with Token
func (c *client) newClientToken(token string) (*gitea.Client, error) {
httpClient := &http.Client{}
Expand All @@ -402,6 +433,15 @@ func (c *client) newClientToken(token string) (*gitea.Client, error) {
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
}
// Forward authentication header in every HTTP request to Gitea
// in reverse proxy authentication mode.
if c.RevProxyAuth {
httpClient.Transport = &authTransport{
headerName: c.RevProxyAuthHeader,
headerValue: c.RevProxyAuthHeaderValue,
underlyingTransport: httpClient.Transport,
}
}
return gitea.NewClient(c.URL, gitea.SetToken(token), gitea.SetHTTPClient(httpClient))
}

Expand Down
25 changes: 23 additions & 2 deletions server/repo.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2018 Drone.IO Inc.
// Copyright 2021 Informatyka Boguslawski sp. z o.o. sp.k., http://www.ib.pl/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -11,6 +12,8 @@
// 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.
//
// This file has been modified by Informatyka Boguslawski sp. z o.o. sp.k.

package server

Expand Down Expand Up @@ -73,9 +76,14 @@ func PostRepo(c *gin.Context) {
return
}

host := httputil.GetURL(c.Request)
if Config.Server.HostInternal != "" {
host = Config.Server.HostInternal
}

link := fmt.Sprintf(
"%s/hook?access_token=%s",
httputil.GetURL(c.Request),
host,
sig,
)

Expand Down Expand Up @@ -203,7 +211,12 @@ func DeleteRepo(c *gin.Context) {
}
}

remote.Deactivate(user, repo, httputil.GetURL(c.Request))
host := httputil.GetURL(c.Request)
if Config.Server.HostInternal != "" {
host = Config.Server.HostInternal
}

remote.Deactivate(user, repo, host)
c.JSON(200, repo)
}

Expand All @@ -222,6 +235,10 @@ func RepairRepo(c *gin.Context) {

// reconstruct the link
host := httputil.GetURL(c.Request)
if Config.Server.HostInternal != "" {
host = Config.Server.HostInternal
}

link := fmt.Sprintf(
"%s/hook?access_token=%s",
host,
Expand Down Expand Up @@ -308,6 +325,10 @@ func MoveRepo(c *gin.Context) {

// reconstruct the link
host := httputil.GetURL(c.Request)
if Config.Server.HostInternal != "" {
host = Config.Server.HostInternal
}

link := fmt.Sprintf(
"%s/hook?access_token=%s",
host,
Expand Down
1 change: 1 addition & 0 deletions server/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ var Config = struct {
Key string
Cert string
Host string
HostInternal string
Port string
Pass string
RepoConfig string
Expand Down
10 changes: 8 additions & 2 deletions shared/token/token.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2018 Drone.IO Inc.
// Copyright 2021 Informatyka Boguslawski sp. z o.o. sp.k., http://www.ib.pl/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -11,6 +12,8 @@
// 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.
//
// This file has been modified by Informatyka Boguslawski sp. z o.o. sp.k.

package token

Expand Down Expand Up @@ -53,12 +56,15 @@ func Parse(raw string, fn SecretFunc) (*Token, error) {
func ParseRequest(r *http.Request, fn SecretFunc) (*Token, error) {
var token = r.Header.Get("Authorization")

// first we attempt to get the token from the
// First we attempt to get the token from the
// authorization header.
if len(token) != 0 {
token = r.Header.Get("Authorization")
fmt.Sscanf(token, "Bearer %s", &token)
return Parse(token, fn)

if parsedToken, err := Parse(token, fn); err == nil {
return parsedToken, nil
}
}

// then we attempt to get the token from the
Expand Down