inkfish

package module
v0.0.0-...-8374b26 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 7, 2022 License: Apache-2.0 Imports: 32 Imported by: 0

README

inkfish

A forward proxy for machines, with access control lists

Build Status

https://hub.docker.com/r/bsycorp/inkfish

About

This is a non-caching forward (aka egress/outbound) proxy, used to implement URL white-listing for applications.

Key features:

  • An outbound proxy designed for machines
  • Can use cloud metadata to determine the identity of an instance
  • Can use Proxy-Authorization header to identify "non-instance" (e.g. codebuild, serverless) workload
  • Per-instance, per-user URL white-lists
  • TLS MITM by default, white-lists all requests at the URL level
  • Optional MITM bypass, by host

Quick Start

You can start the proxy listening on port 8080 with a built-in demo config:

# Start proxy
docker run -p 8080:8080 bsycorp/inkfish:latest /app/inkfish -metadata none -config /config/demo

# Test as anonymous user
export http_proxy=http://localhost:8080
export https_proxy=http://localhost:8080
export no_proxy=127.0.0.1

curl -k https://ifconfig.io/   # This should work for anonymous
curl -k https://google.com/    # This will not work for anonymous user

# Test as an authenticated user
export http_proxy=http://foo:bar@localhost:8080
export https_proxy=http://foo:bar@localhost:8080

curl -k https://google.com/    # This should work

You can find the demo config over here: /testdata/demo_config/. When you are ready to try out your own white-lists, you can mount them into the proxy container from your host:

docker run -v `pwd`/my_config:/config/mine -p 8080:8080 \
    bsycorp/inkfish:latest /app/inkfish -metadata none -config /config/mine

The next step would be to start the proxy in a cloud environment where you can use instance metadata instead of "hard coded" proxy credentials.

How to run

Three distribution mechanisms are offered:

  • Standard docker: bsycorp/inkfish:x.y.z. This is about 30MB and based on minideb. The entry point is a shell.
  • Slim docker: bsycorp/inkfish:x.y.z-slim. A 10MB container with only the static Linux binary. The entry point is the inkfish binary as is customary for containers with no shell.
  • Linux static binary: You can download this from the (releases page)[https://github.com/bsycorp/inkfish/releases].

If you use the slim image, you will also need to mount SSL certificates (configuration of what upstream CAs are trusted) into the container in addition to your proxy configuration files. On a Linux host, this is usually done by volume mounting SSL certs from the host into the container by adding -v /etc/ssl/certs:/etc/ssl/certs to the docker run command.

Command-line arguments

$ docker run bsycorp/inkfish:latest-slim  -h
Usage of /app/inkfish:
  -addr string
    	proxy listen address (default ":8080")
  -cacert string
    	path to CA cert file
  -cakey string
    	path to CA key file
  -client-idle-timeout int
    	client idle timeout (default 300)
  -client-read-timeout int
    	client read timeout
  -client-write-timeout int
    	client write timeout
  -config string
    	path to configuration files (default ".")
  -drain-time int
    	shutdown drain deadline (seconds) (default 30)
  -insecure-test-mode
    	test mode (does not block)
  -metadata string
    	default metadata provider (aws,none) (default "aws")
  -metadata-update-every int
    	metadata update interval (default 10)
  -metrics string
    	metrics provider (none,datadog,prometheus) (default "none")

Configuration file format

The -config argument supplies a path to a directory full of "access control lists" and password file entries.

Passwd files

Passwd files in the config directory must have a .passwd extension. They may contain one or more lines of: <username>:<sha256-of-password> and will be used to verify proxy auth.

It is expected that passwords will generated by infracode / orchestration and have high entropy so a heavyweight password hashing function is not required.

ACL Files

ACL files in the config directory must have a .conf extension. These control what requests will be allowed through the proxy. The general format looks like:

from <user> [user2 user3...]
from ...
url [METHOD,METHOD2] <url-regex> [modifiers]
url ...
s3 <bucket-name> [modifiers]
bypass <host-port-regex>
bypass ...

Blank lines and comments (lines starting with #) are ignored. The from lines gate entry into the ACL. The may be specified as:

  • user:foo - Identifies a client who will supply a proxy-authorization header with a username of foo.
  • tag:foo - Identifies a client whose cloud metadata (e.g. instance ProxyUser tag in AWS) is foo.
  • ANONYMOUS - Identifies a user or system which does not supply a proxy-authorization header and does not have any identifying metadata tags.
  • AUTHENTICATED - Identifies a client with a tag or valid proxy-authorization credentials.
  • ANYONE - Any client. This includes clients with invalid proxy-authorization credentials.

The acl directive is used to permit requests according to a regular expression matching a URL.. You may optionally specify one or more methods in the ACL, causing only requests made with one of the listed methods to match the ACL. Typical "whole-host" acls look like:

  • acl ^http(s)?://foo\.com/

WARNING: it is generally a mistake to forget the trailing /, as this would cause the regular expression to match things like https://foo.com.au/evilthing as well as the intended domain https://foo.com/. Similarly, it is usually a mistake to forget to escape dots with backslashes as this can also cause unintended matches.

A more complex acl example might look like:

  • acl HEAD,GET,POST ^http(s)://api\.foo\.com/v2/

The bypass directive is used to disable TLS MITM for specific hosts. You should supply a regular expression which can be matched directly against the client's CONNECT request. For example:

  • bypass ^my-super-bucket\.ap-southeast-2\.amazonaws\.com:443$

There is also a shorthand for a url regex that includes all AWS S3 URL notations (bucket in path and bucket in host) across all regions

  • s3 my-super-bucket

Modifiers alter the processing of a particular ACL. Currently supported modifiers are:

  • quiet - Suppresses logging of successful requests for the URL pattern or S3 bucket. CONNECT will still be logged, but not individual requests. This is useful for thing like SQS or log upload endpoints where many requests are expected under ordinary circumstances.

Metadata lookup

Rather than distributing proxy credentials, the preferred method of access control in inkfish is via cloud instance metadata.

AWS

For AWS, specify the ProxyUser tag on an instance. So for example if you apply tag of ProxyUser=foo, then in your ACL you would write:

from tag:foo
url ^http(s)?://.*$

To grant instances with that tag unrestricted outbound HTTP(s) access.

Health Check

Configure health checks for the service to hit the listening port on /healthz.

Graceful shutdown

Set drain-time to your shutdown connection drain deadline. The default is 30 seconds. If you have a load balancer forwarding requests to inkfish, your load balancer drain time should be higher than this value.

Documentation

Index

Constants

View Source
const PasswordHashLen = 64 // Length of SHA-256 output, as hex chars
View Source
const ProxyAuthorizationHeader = "Proxy-Authorization"

Variables

View Source
var CA_CERT = []byte(`-----BEGIN CERTIFICATE-----
MIIF9DCCA9ygAwIBAgIJAODqYUwoVjJkMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYD
VQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoM
B0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0
aHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0xNzA0
MDUyMDAwMTBaFw0zNzAzMzEyMDAwMTBaMIGOMQswCQYDVQQGEwJJTDEPMA0GA1UE
CAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAOBgNV
BAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJKoZI
hvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBAJ4Qy+H6hhoY1s0QRcvIhxrjSHaO/RbaFj3rwqcnpOgFq07gRdI9
3c0TFKQJHpgv6feLRhEvX/YllFYu4J35lM9ZcYY4qlKFuStcX8Jm8fqpgtmAMBzP
sqtqDi8M9RQGKENzU9IFOnCV7SAeh45scMuI3wz8wrjBcH7zquHkvqUSYZz035t9
V6WTrHyTEvT4w+lFOVN2bA/6DAIxrjBiF6DhoJqnha0SZtDfv77XpwGG3EhA/qoh
hiYrDruYK7zJdESQL44LwzMPupVigqalfv+YHfQjbhT951IVurW2NJgRyBE62dLr
lHYdtT9tCTCrd+KJNMJ+jp9hAjdIu1Br/kifU4F4+4ZLMR9Ueji0GkkPKsYdyMnq
j0p0PogyvP1l4qmboPImMYtaoFuYmMYlebgC9LN10bL91K4+jLt0I1YntEzrqgJo
WsJztYDw543NzSy5W+/cq4XRYgtq1b0RWwuUiswezmMoeyHZ8BQJe2xMjAOllASD
fqa8OK3WABHJpy4zUrnUBiMuPITzD/FuDx4C5IwwlC68gHAZblNqpBZCX0nFCtKj
YOcI2So5HbQ2OC8QF+zGVuduHUSok4hSy2BBfZ1pfvziqBeetWJwFvapGB44nIHh
WKNKvqOxLNIy7e+TGRiWOomrAWM18VSR9LZbBxpJK7PLSzWqYJYTRCZHAgMBAAGj
UzBRMB0GA1UdDgQWBBR4uDD9Y6x7iUoHO+32ioOcw1ICZTAfBgNVHSMEGDAWgBR4
uDD9Y6x7iUoHO+32ioOcw1ICZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
CwUAA4ICAQAaCEupzGGqcdh+L7BzhX7zyd7yzAKUoLxFrxaZY34Xyj3lcx1XoK6F
AqsH2JM25GixgadzhNt92JP7vzoWeHZtLfstrPS638Y1zZi6toy4E49viYjFk5J0
C6ZcFC04VYWWx6z0HwJuAS08tZ37JuFXpJGfXJOjZCQyxse0Lg0tuKLMeXDCk2Y3
Ba0noeuNyHRoWXXPyiUoeApkVCU5gIsyiJSWOjhJ5hpJG06rQNfNYexgKrrraEin
o0jmEMtJMx5TtD83hSnLCnFGBBq5lkE7jgXME1KsbIE3lJZzRX1mQwUK8CJDYxye
i6M/dzSvy0SsPvz8fTAlprXRtWWtJQmxgWENp3Dv+0Pmux/l+ilk7KA4sMXGhsfr
bvTOeWl1/uoFTPYiWR/ww7QEPLq23yDFY04Q7Un0qjIk8ExvaY8lCkXMgc8i7sGY
VfvOYb0zm67EfAQl3TW8Ky5fl5CcxpVCD360Bzi6hwjYixa3qEeBggOixFQBFWft
8wrkKTHpOQXjn4sDPtet8imm9UYEtzWrFX6T9MFYkBR0/yye0FIh9+YPiTA6WB86
NCNwK5Yl6HuvF97CIH5CdgO+5C7KifUtqTOL8pQKbNwy0S3sNYvB+njGvRpR7pKV
BUnFpB/Atptqr4CUlTXrc5IPLAqAfmwk5IKcwy3EXUbruf9Dwz69YA==
-----END CERTIFICATE-----`)
View Source
var CA_KEY = []byte(`-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----`)

Functions

func GetMetadataMap

func GetMetadataMap(sess *session.Session, targetTag string) (map[string]string, error)

Construct a map from IP -> ProxyUser

func UpdateMetadataFromAWS

func UpdateMetadataFromAWS(sess *session.Session, cache *MetadataCache)

Types

type Acl

type Acl struct {
	From       []string
	Entries    []AclEntry
	MitmBypass []*regexp.Regexp
}

type AclEntry

type AclEntry struct {
	AllMethods bool
	Methods    []string
	Pattern    *regexp.Regexp
	Quiet      bool
}

type CertSigner

type CertSigner struct {
	CA                *tls.Certificate
	TlsConfig         *tls.Config
	CertCache         map[string]tls.Certificate
	CertCacheMutex    *sync.Mutex
	CertHardLifetime  time.Duration
	CertSoftLifetime  time.Duration
	AllowedClockDrift time.Duration
	Now               func() time.Time
}

func NewCertSigner

func NewCertSigner(ca *tls.Certificate) *CertSigner

type ConnectAction

type ConnectAction int
const (
	ConnectMitm ConnectAction = 1 + iota
	ConnectBypass
	ConnectDeny
)

type ConnectLogEntry

type ConnectLogEntry struct {
	RemoteAddr    string
	User          string
	ConnectTarget string
	Result        string
	Reason        string
}

type Inkfish

type Inkfish struct {
	Acls   []Acl
	Passwd []UserEntry

	// Maintains an ip -> tag map, for access control based on instance metadata
	MetadataProvider MetadataProvider

	// Decides whether to allow a CONNECT call by examining the host and port only
	ConnectPolicy func(host string, port int) bool

	// Generates leaf certs for TLS connections.
	CertSigner *CertSigner

	// TLSServerConfig specifies the tls.Config to use when generating leaf cert using CA.
	TLSServerConfig *tls.Config

	// FlushInterval specifies the flush interval to flush to the client while copying
	// the response body. If zero, no periodic flushing is done.
	FlushInterval time.Duration

	// Metrics! Metrics!
	Metrics Metrics

	// Enable test mode (disables blocking of requests!)
	InsecureTestMode bool

	// Shared HTTP transport
	Transport *http.Transport

	//Prometheus Handler
	PromHandler http.Handler
}

func NewInkfish

func NewInkfish(signer *CertSigner) *Inkfish

func (*Inkfish) LoadConfigFromDirectory

func (proxy *Inkfish) LoadConfigFromDirectory(configDir string) error

func (*Inkfish) ServeHTTP

func (proxy *Inkfish) ServeHTTP(w http.ResponseWriter, req *http.Request)

func (*Inkfish) SetCA

func (proxy *Inkfish) SetCA(caCert, caKey []byte) error

Set the CA certificate presented by the proxy from bytes

func (*Inkfish) SetCAFromFiles

func (proxy *Inkfish) SetCAFromFiles(caCertFile, caKeyFile string) error

Set the CA certificate presented by the proxy from PEM files

type MetadataCache

type MetadataCache struct {
	// contains filtered or unexported fields
}

func NewMetadataCache

func NewMetadataCache() *MetadataCache

func (*MetadataCache) Lookup

func (c *MetadataCache) Lookup(k string) (string, bool)

func (*MetadataCache) Replace

func (c *MetadataCache) Replace(newValues map[string]string)

type MetadataProvider

type MetadataProvider interface {
	Lookup(k string) (string, bool)
}

type Metrics

type Metrics struct {
	Registry         metrics.Registry
	AcceptedRequests metrics.Counter
	DeniedRequests   metrics.Counter
	MitmConnects     metrics.Counter
	BypassConnects   metrics.Counter
	DeniedConnects   metrics.Counter
	HandshakeErrors  metrics.Counter
	CertgenErrors    metrics.Counter
	OtherErrors      metrics.Counter
}

func (*Metrics) Init

func (m *Metrics) Init()

func (*Metrics) StartCapture

func (m *Metrics) StartCapture()

type RequestLogEntry

type RequestLogEntry struct {
	RemoteAddr string
	User       string
	Method     string
	Url        url.URL
	Result     string
	Reason     string
}

type UserEntry

type UserEntry struct {
	Username     string
	PasswordHash string
}

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL