chore(deps): update dependency golangci/golangci-lint to v2.7.2 (#3254)

* chore(deps): update dependency golangci/golangci-lint to v2.7.2

* chore(linter): fix gocritic deprecation message issue

Signed-off-by: Jan Larwig <jan@larwig.com>

* chore(lint): fix var-naming: avoid package names that conflict with Go standard library package names (revive)

Signed-off-by: Jan Larwig <jan@larwig.com>

---------

Signed-off-by: Jan Larwig <jan@larwig.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jan Larwig <jan@larwig.com>
This commit is contained in:
renovate[bot]
2026-01-04 10:23:26 +01:00
committed by GitHub
co-authored by Jan Larwig
parent 6a0d821df8
commit 854a74793b
11 changed files with 11 additions and 10 deletions
+88
View File
@@ -0,0 +1,88 @@
package proxyhttp
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"net/http"
"testing"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var ipv4CertData, ipv6CertData []byte
var ipv4CertDataSource, ipv4KeyDataSource options.SecretSource
var ipv6CertDataSource, ipv6KeyDataSource options.SecretSource
var transport *http.Transport
func TestHTTPSuite(t *testing.T) {
logger.SetOutput(GinkgoWriter)
logger.SetErrOutput(GinkgoWriter)
RegisterFailHandler(Fail)
RunSpecs(t, "HTTP")
}
func httpGet(ctx context.Context, url string) (*http.Response, error) {
c := &http.Client{
Transport: transport.Clone(),
}
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
return c.Do(req)
}
var _ = BeforeSuite(func() {
By("Generating a ipv4 self-signed cert for TLS tests", func() {
certBytes, keyBytes, err := util.GenerateCert("127.0.0.1")
Expect(err).ToNot(HaveOccurred())
ipv4CertData = certBytes
certOut := new(bytes.Buffer)
Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed())
ipv4CertDataSource.Value = certOut.Bytes()
keyOut := new(bytes.Buffer)
Expect(pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})).To(Succeed())
ipv4KeyDataSource.Value = keyOut.Bytes()
})
By("Generating a ipv6 self-signed cert for TLS tests", func() {
certBytes, keyBytes, err := util.GenerateCert("::1")
Expect(err).ToNot(HaveOccurred())
ipv6CertData = certBytes
certOut := new(bytes.Buffer)
Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed())
ipv6CertDataSource.Value = certOut.Bytes()
keyOut := new(bytes.Buffer)
Expect(pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})).To(Succeed())
ipv6KeyDataSource.Value = keyOut.Bytes()
})
By("Setting up a http client", func() {
ipv4cert, err := tls.X509KeyPair(ipv4CertDataSource.Value, ipv4KeyDataSource.Value)
Expect(err).ToNot(HaveOccurred())
ipv6cert, err := tls.X509KeyPair(ipv6CertDataSource.Value, ipv6KeyDataSource.Value)
Expect(err).ToNot(HaveOccurred())
ipv4certificate, err := x509.ParseCertificate(ipv4cert.Certificate[0])
Expect(err).ToNot(HaveOccurred())
ipv6certificate, err := x509.ParseCertificate(ipv6cert.Certificate[0])
Expect(err).ToNot(HaveOccurred())
certpool := x509.NewCertPool()
certpool.AddCert(ipv4certificate)
certpool.AddCert(ipv6certificate)
transport = http.DefaultTransport.(*http.Transport).Clone()
transport.TLSClientConfig.RootCAs = certpool
})
})
+308
View File
@@ -0,0 +1,308 @@
package proxyhttp
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"os"
"strings"
"time"
"golang.org/x/sync/errgroup"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options/util"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
)
// Server represents an HTTP or HTTPS server.
type Server interface {
// Start blocks and runs the server.
Start(ctx context.Context) error
}
// Opts contains the information required to set up the server.
type Opts struct {
// Handler is the http.Handler to be used to serve http pages by the server.
Handler http.Handler
// BindAddress is the address the HTTP server should listen on.
BindAddress string
// SecureBindAddress is the address the HTTPS server should listen on.
SecureBindAddress string
// TLS is the TLS configuration for the server.
TLS *options.TLS
// Let testing infrastructure circumvent parsing file descriptors
fdFiles []*os.File
}
// NewServer creates a new Server from the options given.
func NewServer(opts Opts) (Server, error) {
s := &server{
handler: opts.Handler,
}
if len(opts.fdFiles) > 0 {
s.fdFiles = opts.fdFiles
}
if err := s.setupListener(opts); err != nil {
return nil, fmt.Errorf("error setting up listener: %v", err)
}
if err := s.setupTLSListener(opts); err != nil {
return nil, fmt.Errorf("error setting up TLS listener: %v", err)
}
return s, nil
}
// server is an implementation of the Server interface.
type server struct {
handler http.Handler
listener net.Listener
tlsListener net.Listener
// ensure activation.Files are called once
fdFiles []*os.File
}
// setupListener sets the server listener if the HTTP server is enabled.
// The HTTP server can be disabled by setting the BindAddress to "-" or by
// leaving it empty.
func (s *server) setupListener(opts Opts) error {
if opts.BindAddress == "" || opts.BindAddress == "-" {
// No HTTP listener required
return nil
}
// Use fd: as a prefix for systemd socket activation, it's generic
// enough and short.
// The most common usage would be --http-address fd:3.
// This causes oauth2-proxy to just assume that the third fd passed
// to the program is indeed a net.Listener and starts using it
// without setting up a new listener.
if strings.HasPrefix(strings.ToLower(opts.BindAddress), "fd:") {
return s.checkSystemdSocketSupport(opts)
}
networkType := getNetworkScheme(opts.BindAddress)
listenAddr := getListenAddress(opts.BindAddress)
listener, err := net.Listen(networkType, listenAddr)
if err != nil {
return fmt.Errorf("listen (%s, %s) failed: %w", networkType, listenAddr, err)
}
s.listener = listener
return nil
}
func parseCipherSuites(names []string) ([]uint16, error) {
cipherNameMap := make(map[string]uint16)
for _, cipherSuite := range tls.CipherSuites() {
cipherNameMap[cipherSuite.Name] = cipherSuite.ID
}
for _, cipherSuite := range tls.InsecureCipherSuites() {
cipherNameMap[cipherSuite.Name] = cipherSuite.ID
}
result := make([]uint16, len(names))
for i, name := range names {
id, present := cipherNameMap[name]
if !present {
return nil, fmt.Errorf("unknown TLS cipher suite name specified %q", name)
}
result[i] = id
}
return result, nil
}
// setupTLSListener sets the server TLS listener if the HTTPS server is enabled.
// The HTTPS server can be disabled by setting the SecureBindAddress to "-" or by
// leaving it empty.
func (s *server) setupTLSListener(opts Opts) error {
if opts.SecureBindAddress == "" || opts.SecureBindAddress == "-" {
// No HTTPS listener required
return nil
}
config := &tls.Config{
MinVersion: tls.VersionTLS12, // default, override below
MaxVersion: tls.VersionTLS13,
NextProtos: []string{"http/1.1"},
}
if opts.TLS == nil {
return errors.New("no TLS config provided")
}
cert, err := getCertificate(opts.TLS)
if err != nil {
return fmt.Errorf("could not load certificate: %v", err)
}
config.Certificates = []tls.Certificate{cert}
if len(opts.TLS.CipherSuites) > 0 {
cipherSuites, err := parseCipherSuites(opts.TLS.CipherSuites)
if err != nil {
return fmt.Errorf("could not parse cipher suites: %v", err)
}
config.CipherSuites = cipherSuites
}
if len(opts.TLS.MinVersion) > 0 {
switch opts.TLS.MinVersion {
case "TLS1.2":
config.MinVersion = tls.VersionTLS12
case "TLS1.3":
config.MinVersion = tls.VersionTLS13
default:
return errors.New("unknown TLS MinVersion config provided")
}
}
listenAddr := getListenAddress(opts.SecureBindAddress)
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
return fmt.Errorf("listen (%s) failed: %v", listenAddr, err)
}
s.tlsListener = tls.NewListener(tcpKeepAliveListener{listener.(*net.TCPListener)}, config)
return nil
}
// Start starts the HTTP and HTTPS server if applicable.
// It will block until the context is cancelled.
// If any errors occur, only the first error will be returned.
func (s *server) Start(ctx context.Context) error {
g, groupCtx := errgroup.WithContext(ctx)
if s.listener != nil {
g.Go(func() error {
if err := s.startServer(groupCtx, s.listener); err != nil {
return fmt.Errorf("error starting insecure server: %v", err)
}
return nil
})
}
if s.tlsListener != nil {
g.Go(func() error {
if err := s.startServer(groupCtx, s.tlsListener); err != nil {
return fmt.Errorf("error starting secure server: %v", err)
}
return nil
})
}
return g.Wait()
}
// startServer creates and starts a new server with the given listener.
// When the given context is cancelled the server will be shutdown.
// If any errors occur, only the first error will be returned.
func (s *server) startServer(ctx context.Context, listener net.Listener) error {
srv := &http.Server{Handler: s.handler, ReadHeaderTimeout: time.Minute}
g, groupCtx := errgroup.WithContext(ctx)
g.Go(func() error {
<-groupCtx.Done()
if err := srv.Shutdown(context.Background()); err != nil {
return fmt.Errorf("error shutting down server: %v", err)
}
return nil
})
g.Go(func() error {
if err := srv.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
return fmt.Errorf("could not start server: %v", err)
}
return nil
})
return g.Wait()
}
// getNetworkScheme gets the scheme for the HTTP server.
func getNetworkScheme(addr string) string {
var scheme string
i := strings.Index(addr, "://")
if i > -1 {
scheme = addr[0:i]
}
switch scheme {
case "", "http":
return "tcp"
default:
return scheme
}
}
// getListenAddress gets the address for the HTTP server.
func getListenAddress(addr string) string {
slice := strings.SplitN(addr, "//", 2)
return slice[len(slice)-1]
}
// getCertificate loads the certificate data from the TLS config.
func getCertificate(opts *options.TLS) (tls.Certificate, error) {
keyData, err := getSecretValue(opts.Key)
if err != nil {
return tls.Certificate{}, fmt.Errorf("could not load key data: %v", err)
}
certData, err := getSecretValue(opts.Cert)
if err != nil {
return tls.Certificate{}, fmt.Errorf("could not load cert data: %v", err)
}
cert, err := tls.X509KeyPair(certData, keyData)
if err != nil {
return tls.Certificate{}, fmt.Errorf("could not parse certificate data: %v", err)
}
return cert, nil
}
// getSecretValue wraps util.GetSecretValue so that we can return an error if no
// source is provided.
func getSecretValue(src *options.SecretSource) ([]byte, error) {
if src == nil {
return nil, errors.New("no configuration provided")
}
return util.GetSecretValue(src)
}
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by so that dead TCP connections (e.g. closing laptop
// mid-download) eventually go away.
type tcpKeepAliveListener struct {
*net.TCPListener
}
// Accept implements the TCPListener interface.
// It sets the keep alive period to 3 minutes for each connection.
func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
tc, err := ln.AcceptTCP()
if err != nil {
return nil, err
}
err = tc.SetKeepAlive(true)
if err != nil {
logger.Errorf("Error setting Keep-Alive: %v", err)
}
err = tc.SetKeepAlivePeriod(3 * time.Minute)
if err != nil {
logger.Printf("Error setting Keep-Alive period: %v", err)
}
return tc, nil
}
+35
View File
@@ -0,0 +1,35 @@
package proxyhttp
import (
"context"
"golang.org/x/sync/errgroup"
)
// NewServerGroup creates a new Server to start and gracefully stop a collection
// of Servers.
func NewServerGroup(servers ...Server) Server {
return &serverGroup{
servers: servers,
}
}
// serverGroup manages the starting and graceful shutdown of a collection of
// servers.
type serverGroup struct {
servers []Server
}
// Start runs the servers in the server group.
func (s *serverGroup) Start(ctx context.Context) error {
g, groupCtx := errgroup.WithContext(ctx)
for _, server := range s.servers {
srv := server
g.Go(func() error {
return srv.Start(groupCtx)
})
}
return g.Wait()
}
+102
View File
@@ -0,0 +1,102 @@
package proxyhttp
import (
"context"
"errors"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Server Group", func() {
var m1, m2, m3 *mockServer
var ctx context.Context
var cancel context.CancelFunc
var group Server
BeforeEach(func() {
ctx, cancel = context.WithCancel(context.Background())
m1 = newMockServer()
m2 = newMockServer()
m3 = newMockServer()
group = NewServerGroup(m1, m2, m3)
})
AfterEach(func() {
cancel()
})
It("starts each server in the group", func() {
go func() {
defer GinkgoRecover()
Expect(group.Start(ctx)).To(Succeed())
}()
Eventually(m1.started).Should(BeClosed(), "mock server 1 not started")
Eventually(m2.started).Should(BeClosed(), "mock server 2 not started")
Eventually(m3.started).Should(BeClosed(), "mock server 3 not started")
})
It("stop each server in the group when the context is cancelled", func() {
go func() {
defer GinkgoRecover()
Expect(group.Start(ctx)).To(Succeed())
}()
cancel()
Eventually(m1.stopped).Should(BeClosed(), "mock server 1 not stopped")
Eventually(m2.stopped).Should(BeClosed(), "mock server 2 not stopped")
Eventually(m3.stopped).Should(BeClosed(), "mock server 3 not stopped")
})
It("stop each server in the group when the an error occurs", func() {
err := errors.New("server error")
go func() {
defer GinkgoRecover()
Expect(group.Start(ctx)).To(MatchError(err))
}()
m2.errors <- err
Eventually(m1.stopped).Should(BeClosed(), "mock server 1 not stopped")
Eventually(m2.stopped).Should(BeClosed(), "mock server 2 not stopped")
Eventually(m3.stopped).Should(BeClosed(), "mock server 3 not stopped")
})
})
// mockServer is used to test the server group can start
// and stop multiple servers simultaneously.
type mockServer struct {
started chan struct{}
startClosed bool
stopped chan struct{}
stopClosed bool
errors chan error
}
func newMockServer() *mockServer {
return &mockServer{
started: make(chan struct{}),
stopped: make(chan struct{}),
errors: make(chan error),
}
}
func (m *mockServer) Start(ctx context.Context) error {
if !m.startClosed {
close(m.started)
m.startClosed = true
}
defer func() {
if !m.stopClosed {
close(m.stopped)
m.stopClosed = true
}
}()
select {
case <-ctx.Done():
return nil
case err := <-m.errors:
return err
}
}
File diff suppressed because it is too large Load Diff
+55
View File
@@ -0,0 +1,55 @@
//go:build !windows
// +build !windows
package proxyhttp
import (
"errors"
"fmt"
"net"
"strconv"
"github.com/coreos/go-systemd/activation"
)
// listenFdsStart corresponds to `SD_LISTEN_FDS_START`.
// Since the 3 first file descriptors in every linux process is
// stdin, stdout and stderr. The first usable file descriptor is 3.
// systemd-socket-activate will always assume that the first socket will be
// 3 and the rest follow.
const (
listenFdsStart = 3
)
// convert a string filedescriptor to an actual listener
func (s *server) fdToListener(bindAddress string) (net.Listener, error) {
fd, err := strconv.Atoi(bindAddress)
if err != nil {
return nil, errors.New("listen failed: fd with name is not implemented yet")
}
fdIndex := fd - listenFdsStart
if len(s.fdFiles) == 0 {
s.fdFiles = activation.Files(true)
}
l := len(s.fdFiles)
if fdIndex < 0 || fdIndex >= l || l == 0 {
return nil, errors.New("listen failed: fd outside of range of available file descriptors")
}
return net.FileListener(s.fdFiles[fdIndex])
}
func (s *server) checkSystemdSocketSupport(opts Opts) error {
listenAddr := opts.BindAddress[3:]
listener, err := s.fdToListener(listenAddr)
if err != nil {
err = fmt.Errorf("listen (file, %s) failed: %w", listenAddr, err)
return err
}
s.listener = listener
return nil
}
+17
View File
@@ -0,0 +1,17 @@
//go:build windows
// +build windows
package proxyhttp
import (
"fmt"
"strings"
)
func (s *server) checkSystemdSocketSupport(opts Opts) error {
if strings.HasPrefix(strings.ToLower(opts.BindAddress), "fd:") {
listenAddr := opts.BindAddress[3:]
return fmt.Errorf("listen (file, %s) failed: systemd sockets are not supported on windows", listenAddr)
}
return nil
}