Skip to content

Commit

Permalink
Merge pull request tinkerbell#451 from jacobweinstock/remove-dep-pack…
Browse files Browse the repository at this point in the history
…ethost

Remove github.com/packethost/xff dependency:

## Description

<!--- Please describe what this PR is going to change -->
This repo is not maintained. Also, add license header for code copied from the no longer maintainer github.com/equinix-labs/otel-init-go repo.

## Why is this needed

<!--- Link to issue you have raised -->

Fixes: #

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->


## How are existing users impacted? What migration steps/scripts do we need?

<!--- Fixes a bug, unblocks installation, removes a component of the stack etc -->
<!--- Requires a DB migration script, etc. -->


## Checklist:

I have:

- [ ] updated the documentation and/or roadmap (if required)
- [ ] added unit or e2e tests
- [ ] provided instructions on how to upgrade
  • Loading branch information
jacobweinstock authored Dec 4, 2024
2 parents 3dbd175 + c9866a6 commit 0a4a388
Show file tree
Hide file tree
Showing 6 changed files with 328 additions and 5 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ require (
github.com/go-logr/stdr v1.2.2
github.com/google/go-cmp v0.6.0
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475
github.com/packethost/xff v0.0.0-20190305172552-d3e9190c41b3
github.com/peterbourgon/ff/v3 v3.4.0
github.com/prometheus/client_golang v1.20.5
github.com/stretchr/testify v1.9.0
github.com/tinkerbell/ipxedust v0.0.0-20241108174245-aa0c0298057d
github.com/tinkerbell/tink v0.12.1
github.com/vishvananda/netlink v1.3.0
Expand Down Expand Up @@ -78,6 +78,7 @@ require (
github.com/pin/tftp/v3 v3.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/xattr v0.4.9 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,6 @@ github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
github.com/packethost/xff v0.0.0-20190305172552-d3e9190c41b3 h1:QcUVLV3NdkCVv4DxQkhgkxTsRvuXn+ZuSqD93mQYouc=
github.com/packethost/xff v0.0.0-20190305172552-d3e9190c41b3/go.mod h1:nt3WBqCaQsbnxYVBoB4pF+F584z9PjdSVm29iu4gIBg=
github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc=
github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
Expand Down
3 changes: 1 addition & 2 deletions internal/ipxe/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"time"

"github.com/go-logr/logr"
"github.com/packethost/xff"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
Expand Down Expand Up @@ -44,7 +43,7 @@ func (s *Config) ServeHTTP(ctx context.Context, addr string, handlers HandlerMap
// add X-Forwarded-For support if trusted proxies are configured
var xffHandler http.Handler
if len(s.TrustedProxies) > 0 {
xffmw, err := xff.New(xff.Options{
xffmw, err := newXFF(xffOptions{
AllowedSubnets: s.TrustedProxies,
})
if err != nil {
Expand Down
151 changes: 151 additions & 0 deletions internal/ipxe/http/xff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
https://github.com/sebest/xff
Copyright (c) 2015 Sebastien Estienne ([email protected])
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package http

import (
"net"
"net/http"
"strings"
)

// xffOptions is a configuration container to setup the XFF middleware.
type xffOptions struct {
// AllowedSubnets is a list of Subnets from which we will accept the
// X-Forwarded-For header.
// If this list is empty we will accept every Subnets (default).
AllowedSubnets []string
// Debugging flag adds additional output to debug server side XFF issues.
Debug bool
}

// xff http handler.
type xff struct {
// Set to true if all IPs or Subnets are allowed.
allowAll bool
// List of IP subnets that are allowed.
allowedMasks []net.IPNet
}

// New creates a new XFF handler with the provided options.
func newXFF(options xffOptions) (*xff, error) {
allowedMasks, err := toMasks(options.AllowedSubnets)
if err != nil {
return nil, err
}
xff := &xff{
allowAll: len(options.AllowedSubnets) == 0,
allowedMasks: allowedMasks,
}

return xff, nil
}

// Handler updates RemoteAdd from X-Fowarded-For Headers.
func (xff *xff) Handler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.RemoteAddr = getRemoteAddrIfAllowed(r, xff.allowed)
h.ServeHTTP(w, r)
})
}

// getRemoteAddrIfAllowed parses the given request, resolves the X-Forwarded-For header
// and returns the resolved remote address if allowed.
func getRemoteAddrIfAllowed(r *http.Request, allowed func(sip string) bool) string {
if xffh := r.Header.Get("X-Forwarded-For"); xffh != "" {
if sip, sport, err := net.SplitHostPort(r.RemoteAddr); err == nil && sip != "" {
if allowed(sip) {
if xip := parse(xffh, allowed); xip != "" {
return net.JoinHostPort(xip, sport)
}
}
}
}
return r.RemoteAddr
}

// parse parses the value of the X-Forwarded-For Header and returns the IP address.
func parse(ipList string, allowed func(string) bool) string {
ips := strings.Split(ipList, ",")
if len(ips) == 0 {
return ""
}

// simple case of only 1 proxy
if len(ips) == 1 {
ip := strings.TrimSpace(ips[0])
if net.ParseIP(ip) != nil {
return ip
}
return ""
}

// multiple proxies
// common form of X-F-F is: client, proxy1, proxy2, ... proxyN-1
// so we verify backwards and return the first unallowed/untrusted proxy
lastIP := ""
for i := len(ips) - 1; i >= 0; i-- {
ip := strings.TrimSpace(ips[i])
if net.ParseIP(ip) == nil {
break
}
lastIP = ip
if !allowed(ip) {
break
}
}
return lastIP
}

// converts a list of subnets' string to a list of net.IPNet.
func toMasks(ips []string) (masks []net.IPNet, err error) {
for _, cidr := range ips {
var network *net.IPNet
_, network, err = net.ParseCIDR(cidr)
if err != nil {
return
}
masks = append(masks, *network)
}
return
}

// checks that the IP is allowed.
func (xff *xff) allowed(sip string) bool {
if xff.allowAll {
return true
} else if ip := net.ParseIP(sip); ip != nil && ipInMasks(ip, xff.allowedMasks) {
return true
}
return false
}

// checks if a net.IP is in a list of net.IPNet.
func ipInMasks(ip net.IP, masks []net.IPNet) bool {
for _, mask := range masks {
if mask.Contains(ip) {
return true
}
}
return false
}
158 changes: 158 additions & 0 deletions internal/ipxe/http/xff_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
https://github.com/sebest/xff
Copyright (c) 2015 Sebastien Estienne ([email protected])
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package http

import (
"net"
"testing"

"github.com/stretchr/testify/assert"
)

func TestParse_none(t *testing.T) {
res := parse("", nil)
assert.Equal(t, "", res)
}

func allowAll(string) bool { return true }

func TestParse_localhost(t *testing.T) {
res := parse("127.0.0.1", allowAll)
assert.Equal(t, "127.0.0.1", res)
}

func TestParse_invalid(t *testing.T) {
res := parse("invalid", allowAll)
assert.Equal(t, "", res)
}

func TestParse_invalid_sioux(t *testing.T) {
res := parse("123#1#2#3", allowAll)
assert.Equal(t, "", res)
}

func TestParse_invalid_private_lookalike(t *testing.T) {
res := parse("102.3.2.1", allowAll)
assert.Equal(t, "102.3.2.1", res)
}

func TestParse_valid(t *testing.T) {
res := parse("68.45.152.220", allowAll)
assert.Equal(t, "68.45.152.220", res)
}

func TestParse_multi_first(t *testing.T) {
res := parse("12.13.14.15, 68.45.152.220", allowAll)
assert.Equal(t, "12.13.14.15", res)
}

func TestParse_multi_with_invalid(t *testing.T) {
res := parse("invalid, 190.57.149.90", allowAll)
assert.Equal(t, "190.57.149.90", res)
}

func TestParse_multi_with_invalid2(t *testing.T) {
res := parse("190.57.149.90, invalid", allowAll)
assert.Equal(t, "", res)
}

func TestParse_multi_with_invalid_sioux(t *testing.T) {
res := parse("190.57.149.90, 123#1#2#3", allowAll)
assert.Equal(t, "", res)
}

func TestParse_ipv6_with_port(t *testing.T) {
res := parse("2604:2000:71a9:bf00:f178:a500:9a2d:670d", allowAll)
assert.Equal(t, "2604:2000:71a9:bf00:f178:a500:9a2d:670d", res)
}

func TestToMasks_empty(t *testing.T) {
ips := []string{}
masks, err := toMasks(ips)
assert.Empty(t, masks)
assert.Nil(t, err)
}

func TestToMasks(t *testing.T) {
ips := []string{"127.0.0.1/32", "10.0.0.0/8"}
masks, err := toMasks(ips)
_, ipnet1, _ := net.ParseCIDR("127.0.0.1/32")
_, ipnet2, _ := net.ParseCIDR("10.0.0.0/8")
assert.Equal(t, []net.IPNet{*ipnet1, *ipnet2}, masks)
assert.Nil(t, err)
}

func TestToMasks_error(t *testing.T) {
ips := []string{"error"}
masks, err := toMasks(ips)
assert.Empty(t, masks)
assert.Equal(t, &net.ParseError{Type: "CIDR address", Text: "error"}, err)
}

func TestAllowed_all(t *testing.T) {
m, _ := newXFF(xffOptions{
AllowedSubnets: []string{},
})
assert.True(t, m.allowed("127.0.0.1"))
}

func TestAllowed_yes(t *testing.T) {
m, _ := newXFF(xffOptions{
AllowedSubnets: []string{"127.0.0.0/16"},
})
assert.True(t, m.allowed("127.0.0.1"))

m, _ = newXFF(xffOptions{
AllowedSubnets: []string{"127.0.0.1/32"},
})
assert.True(t, m.allowed("127.0.0.1"))
}

func TestAllowed_no(t *testing.T) {
m, _ := newXFF(xffOptions{
AllowedSubnets: []string{"127.0.0.0/16"},
})
assert.False(t, m.allowed("127.1.0.1"))

m, _ = newXFF(xffOptions{
AllowedSubnets: []string{"127.0.0.1/32"},
})
assert.False(t, m.allowed("127.0.0.2"))
}

func TestParseUnallowedMidway(t *testing.T) {
m, _ := newXFF(xffOptions{
AllowedSubnets: []string{"127.0.0.0/16"},
})
res := parse("1.1.1.1, 8.8.8.8, 127.0.0.1, 127.0.0.2", m.allowed)
assert.Equal(t, "8.8.8.8", res)
}

func TestParseMany(t *testing.T) {
m, _ := newXFF(xffOptions{
AllowedSubnets: []string{"127.0.0.0/16"},
})
res := parse("1.1.1.1, 127.0.0.1, 127.0.0.2, 127.0.0.3", m.allowed)
assert.Equal(t, "1.1.1.1", res)
}
16 changes: 16 additions & 0 deletions internal/otel/otel.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/*
https://github.com/equinix-labs/otel-init-go
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
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.
*/
package otel

import (
Expand Down

0 comments on commit 0a4a388

Please sign in to comment.