1
0
Fork 0
mirror of https://code.forgejo.org/forgejo/runner.git synced 2025-08-06 17:40:58 +00:00
forgejo-runner/act/cacheproxy/handler.go

162 lines
3.5 KiB
Go
Raw Normal View History

package cacheproxy
import (
2024-10-24 16:24:28 +02:00
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"net"
"net/http"
2024-10-25 17:15:43 +02:00
"net/http/httputil"
"net/url"
"time"
"github.com/julienschmidt/httprouter"
"github.com/sirupsen/logrus"
"github.com/nektos/act/pkg/common"
)
const (
urlBase = "/_apis/artifactcache"
)
type Handler struct {
router *httprouter.Router
listener net.Listener
server *http.Server
logger logrus.FieldLogger
outboundIP string
cacheServerHost string
2024-10-25 17:15:43 +02:00
repositoryName string
repositorySecret string
}
2024-10-25 17:15:43 +02:00
func StartHandler(repoName string, targetHost string, outboundIP string, port uint16, cacheSecret string, logger logrus.FieldLogger) (*Handler, error) {
h := &Handler{}
if logger == nil {
discard := logrus.New()
discard.Out = io.Discard
logger = discard
}
logger = logger.WithField("module", "artifactcache")
h.logger = logger
h.repositoryName = repoName
2024-10-25 17:15:43 +02:00
repoSecret, err := calculateMAC(repoName, cacheSecret)
if err != nil {
return nil, fmt.Errorf("unable to decode cacheSecret")
}
h.repositorySecret = repoSecret
if outboundIP != "" {
h.outboundIP = outboundIP
} else if ip := common.GetOutboundIP(); ip == nil {
return nil, fmt.Errorf("unable to determine outbound IP address")
} else {
h.outboundIP = ip.String()
}
2024-10-25 17:15:43 +02:00
h.cacheServerHost = targetHost
proxy, err := h.newReverseProxy(targetHost)
if err != nil {
return nil, fmt.Errorf("unable to set up proxy to target host")
}
router := httprouter.New()
2024-10-25 17:15:43 +02:00
router.HandlerFunc("GET", urlBase, proxyRequestHandler(proxy))
router.HandlerFunc("POST", urlBase, proxyRequestHandler(proxy))
router.HandlerFunc("PATCH", urlBase, proxyRequestHandler(proxy))
h.router = router
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) // listen on all interfaces
if err != nil {
return nil, err
}
server := &http.Server{
ReadHeaderTimeout: 2 * time.Second,
Handler: router,
}
go func() {
if err := server.Serve(listener); err != nil && errors.Is(err, net.ErrClosed) {
logger.Errorf("http serve: %v", err)
}
}()
h.listener = listener
h.server = server
return h, nil
}
2024-10-25 17:15:43 +02:00
func proxyRequestHandler(proxy *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
proxy.ServeHTTP(w, r)
}
}
func (h *Handler) newReverseProxy(targetHost string) (*httputil.ReverseProxy, error) {
url, err := url.Parse(targetHost)
if err != nil {
return nil, err
}
proxy := httputil.NewSingleHostReverseProxy(url)
proxy.Rewrite = func(r *httputil.ProxyRequest) { h.injectAuth(r) }
return proxy, nil
}
func (h *Handler) injectAuth(r *httputil.ProxyRequest) {
r.Out.SetBasicAuth(h.repositoryName, h.repositorySecret)
}
func (h *Handler) ExternalURL() string {
// TODO: make the external url configurable if necessary
return fmt.Sprintf("http://%s:%d",
h.outboundIP,
h.listener.Addr().(*net.TCPAddr).Port)
}
func (h *Handler) Close() error {
if h == nil {
return nil
}
var retErr error
if h.server != nil {
err := h.server.Close()
if err != nil {
retErr = err
}
h.server = nil
}
if h.listener != nil {
err := h.listener.Close()
if errors.Is(err, net.ErrClosed) {
err = nil
}
if err != nil {
retErr = err
}
h.listener = nil
}
return retErr
}
2024-10-25 17:15:43 +02:00
func calculateMAC(repoName string, cacheSecret string) (string, error) {
sec, err := hex.DecodeString(cacheSecret)
if err != nil {
2024-10-25 17:15:43 +02:00
return "", err
}
2024-10-25 17:15:43 +02:00
mac := hmac.New(sha256.New, sec)
mac.Write([]byte(repoName))
macBytes := mac.Sum(nil)
return hex.EncodeToString(macBytes), nil
}