diff --git a/hack/build-images.sh b/hack/build-images.sh index eca942814cca..192a926d4fab 100755 --- a/hack/build-images.sh +++ b/hack/build-images.sh @@ -96,6 +96,7 @@ image "${tag_prefix}-haproxy-router" images/router/haproxy image "${tag_prefix}-keepalived-ipfailover" images/ipfailover/keepalived image "${tag_prefix}-docker-registry" images/dockerregistry image "${tag_prefix}-egress-router" images/egress/router +image "${tag_prefix}-egress-http-proxy" images/egress/http-proxy image "${tag_prefix}-federation" images/federation # images that depend on "${tag_prefix} image "${tag_prefix}-gitserver" examples/gitserver diff --git a/images/egress/http-proxy/.cccp.yml b/images/egress/http-proxy/.cccp.yml new file mode 100644 index 000000000000..ae59670e5eb4 --- /dev/null +++ b/images/egress/http-proxy/.cccp.yml @@ -0,0 +1 @@ +job-id: origin-egress-http-proxy diff --git a/images/egress/http-proxy/Dockerfile b/images/egress/http-proxy/Dockerfile new file mode 100644 index 000000000000..259fa7090877 --- /dev/null +++ b/images/egress/http-proxy/Dockerfile @@ -0,0 +1,17 @@ +# +# This is the egress router HTTP proxy for OpenShift Origin +# +# The standard name for this image is openshift/origin-egress-http-proxy + +FROM openshift/origin-base + +RUN INSTALL_PKGS="squid" && \ + yum install -y $INSTALL_PKGS && \ + rpm -V $INSTALL_PKGS && \ + yum clean all && \ + rmdir /var/log/squid /var/spool/squid && \ + rm -f /etc/squid/squid.conf + +ADD egress-http-proxy.sh /bin/egress-http-proxy.sh + +ENTRYPOINT /bin/egress-http-proxy.sh diff --git a/images/egress/http-proxy/egress-http-proxy.sh b/images/egress/http-proxy/egress-http-proxy.sh new file mode 100755 index 000000000000..fa4b832218b5 --- /dev/null +++ b/images/egress/http-proxy/egress-http-proxy.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +# OpenShift egress HTTP proxy setup script + +set -o errexit +set -o nounset +set -o pipefail + +function die() { + echo "$*" 1>&2 + exit 1 +} + +if [[ -z "${EGRESS_HTTP_PROXY_DESTINATION}" ]]; then + die "No EGRESS_HTTP_PROXY_DESTINATION specified" +fi + +IPADDR_REGEX="[[:xdigit:].:]*[.:][[:xdigit:].:]+" +OPT_CIDR_MASK_REGEX="(/[[:digit:]]+)?" +HOSTNAME_REGEX="[[:alnum:]][[:alnum:].-]+" +DOMAIN_REGEX="\*\.${HOSTNAME_REGEX}" + +function generate_acls() { + n=0 + saw_wildcard= + while read dest; do + if [[ "${dest}" =~ ^\w*$ || "${dest}" =~ ^# ]]; then + # comment or blank line + continue + fi + n=$(($n + 1)) + + if [[ "${dest}" == "*" ]]; then + saw_wildcard=1 + continue + elif [[ -n "${saw_wildcard}" ]]; then + die "Wildcard must be last rule, if present" + fi + + if [[ "${dest}" =~ ^! ]]; then + rule=deny + dest="${dest#!}" + else + rule=allow + fi + + echo "" + if [[ "${dest}" =~ ^${IPADDR_REGEX}${OPT_CIDR_MASK_REGEX}$ ]]; then + echo acl dest$n dst "${dest}" + echo http_access "${rule}" dest$n + elif [[ "${dest}" =~ ^${DOMAIN_REGEX}$ ]]; then + echo acl dest$n dstdomain "${dest#\*}" + echo http_access "${rule}" dest$n + elif [[ "${dest}" =~ ^${HOSTNAME_REGEX}$ ]]; then + echo acl dest$n dstdomain "${dest}" + echo http_access "${rule}" dest$n + else + die "Bad destination '${dest}'" + fi + done <<< "${EGRESS_HTTP_PROXY_DESTINATION}" + + echo "" + if [[ -n "${saw_wildcard}" ]]; then + echo "http_access allow all" + else + echo "http_access deny all" + fi +} + +if [[ "${EGRESS_HTTP_PROXY_MODE:-}" == "unit-test" ]]; then + generate_acls + exit 0 +fi + +CONF=/etc/squid/squid.conf +rm -f ${CONF} + +cat > ${CONF} <> ${CONF} + +echo "Running squid with config:" +sed -e 's/^/ /' ${CONF} +echo "" +echo "" + +exec squid -N diff --git a/images/egress/http-proxy/egress_http_proxy_test.go b/images/egress/http-proxy/egress_http_proxy_test.go new file mode 100644 index 000000000000..4f0db3966320 --- /dev/null +++ b/images/egress/http-proxy/egress_http_proxy_test.go @@ -0,0 +1,159 @@ +package egress_http_proxy_test + +import ( + "fmt" + "os/exec" + "strings" + "testing" +) + +func TestGenerateSquidConf(t *testing.T) { + tests := []struct { + in string + out string + }{ + { + in: "*", + out: ` +http_access allow all +`, + }, + { + in: "example.com", + out: ` +acl dest1 dstdomain example.com +http_access allow dest1 + +http_access deny all +`, + }, + { + in: "!example.com", + out: ` +acl dest1 dstdomain example.com +http_access deny dest1 + +http_access deny all +`, + }, + { + in: "*.example.com", + out: ` +acl dest1 dstdomain .example.com +http_access allow dest1 + +http_access deny all +`, + }, + { + in: "192.168.1.1", + out: ` +acl dest1 dst 192.168.1.1 +http_access allow dest1 + +http_access deny all +`, + }, + { + in: "192.168.1.0/24", + out: ` +acl dest1 dst 192.168.1.0/24 +http_access allow dest1 + +http_access deny all +`, + }, + { + in: ` +!*.example.net +* +`, + out: ` +acl dest1 dstdomain .example.net +http_access deny dest1 + +http_access allow all +`, + }, + { + in: ` +# HTTP proxy config + +!*.bad.example.com +*.example.com + +192.168.0.0/16 +fe80::/10 + +# end +`, + out: ` +acl dest1 dstdomain .bad.example.com +http_access deny dest1 + +acl dest2 dstdomain .example.com +http_access allow dest2 + +acl dest3 dst 192.168.0.0/16 +http_access allow dest3 + +acl dest4 dst fe80::/10 +http_access allow dest4 + +http_access deny all +`, + }, + } + + for n, test := range tests { + cmd := exec.Command("./egress-http-proxy.sh") + cmd.Env = []string{ + fmt.Sprintf("EGRESS_HTTP_PROXY_MODE=unit-test"), + fmt.Sprintf("EGRESS_HTTP_PROXY_DESTINATION=%s", test.in), + } + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("test %d expected output %q but got error %v / %q", n+1, test.out, err, string(out)) + } + if string(out) != test.out { + t.Fatalf("test %d expected output %q but got %q", n+1, test.out, string(out)) + } + } +} + +func TestGenerateSquidConfBad(t *testing.T) { + tests := []struct { + in string + err string + }{ + { + in: "", + err: "No EGRESS_HTTP_PROXY_DESTINATION specified", + }, + { + in: "*\nexample.com", + err: "Wildcard must be last rule, if present", + }, + { + in: "foo bar", + err: "Bad destination 'foo bar'", + }, + } + + for n, test := range tests { + cmd := exec.Command("./egress-http-proxy.sh") + cmd.Env = []string{ + fmt.Sprintf("EGRESS_HTTP_PROXY_MODE=unit-test"), + fmt.Sprintf("EGRESS_HTTP_PROXY_DESTINATION=%s", test.in), + } + out, err := cmd.CombinedOutput() + out_lines := strings.Split(string(out), "\n") + got := out_lines[len(out_lines)-2] + if err == nil { + t.Fatalf("test %d expected error %q but got output %q", n+1, test.err, got) + } + if got != test.err { + t.Fatalf("test %d expected output %q but got %q", n+1, test.err, got) + } + } +} diff --git a/images/egress/router/egress-router.sh b/images/egress/router/egress-router.sh index 992b30953fbe..ae37660025fc 100755 --- a/images/egress/router/egress-router.sh +++ b/images/egress/router/egress-router.sh @@ -15,10 +15,6 @@ if [[ ! "${EGRESS_SOURCE:-}" =~ ^${IP_REGEX}$ ]]; then echo "EGRESS_SOURCE unspecified or invalid" exit 1 fi -if [[ -z "${EGRESS_DESTINATION:-}" ]]; then - echo "EGRESS_DESTINATION unspecified" - exit 1 -fi if [[ ! "${EGRESS_GATEWAY:-}" =~ ^${IP_REGEX}$ ]]; then echo "EGRESS_GATEWAY unspecified or invalid" exit 1 @@ -48,6 +44,11 @@ function setup_network() { } function gen_iptables_rules() { + if [[ -z "${EGRESS_DESTINATION:-}" ]]; then + echo "EGRESS_DESTINATION unspecified" + exit 1 + fi + did_fallback= while read dest; do if [[ "${dest}" =~ ^${BLANK_LINE_OR_COMMENT_REGEX}$ ]]; then @@ -122,6 +123,10 @@ case "${EGRESS_ROUTER_MODE:=legacy}" in wait_until_killed ;; + http-proxy) + setup_network + ;; + unit-test) gen_iptables_rules ;;