Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

s3proxy: add initial implementation #2385

Merged
merged 2 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions bazel/oci/containers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ def containers():
"repotag_file": "//bazel/release:libvirt_tag.txt",
"used_by": ["config"],
},
{
"identifier": "s3proxy",
"image_name": "s3proxy",
"name": "s3proxy",
"oci": "//s3proxy/cmd:s3proxy",
"repotag_file": "//bazel/release:s3proxy_tag.txt",
"used_by": ["config"],
},
]

def helm_containers():
Expand Down
10 changes: 10 additions & 0 deletions bazel/toolchains/go_module_deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -3050,6 +3050,7 @@ def go_dependencies():
sum = "h1:YjkZLJ7K3inKgMZ0wzCU9OHqc+UqMQyXsPXnf3Cl2as=",
version = "v1.9.2",
)

go_repository(
name = "com_github_hexops_gotextdiff",
build_file_generation = "on",
Expand Down Expand Up @@ -5033,6 +5034,15 @@ def go_dependencies():
sum = "h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=",
version = "v1.2.0",
)
go_repository(
name = "com_github_tink_crypto_tink_go_v2",
build_file_generation = "on",
build_file_proto_mode = "disable_global",
importpath = "github.com/tink-crypto/tink-go/v2",
replace = "github.com/derpsteb/tink-go/v2",
sum = "h1:FVii9oXvddz9sFir5TRYjQKrzJLbVD/hibT+SnRSDzg=",
version = "v2.0.0-20231002051717-a808e454eed6",
)

go_repository(
name = "com_github_titanous_rocacheck",
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ replace (
github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/api => ./operators/constellation-node-operator/api
github.com/google/go-tpm => github.com/thomasten/go-tpm v0.0.0-20230629092004-f43f8e2a59eb
github.com/martinjungblut/go-cryptsetup => github.com/daniel-weisse/go-cryptsetup v0.0.0-20230705150314-d8c07bd1723c
github.com/tink-crypto/tink-go/v2 v2.0.0 => github.com/derpsteb/tink-go/v2 v2.0.0-20231002051717-a808e454eed6
)

require (
Expand Down Expand Up @@ -108,6 +109,7 @@ require (
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.4
github.com/theupdateframework/go-tuf v0.5.2
github.com/tink-crypto/tink-go/v2 v2.0.0
go.uber.org/goleak v1.2.1
go.uber.org/zap v1.26.0
golang.org/x/crypto v0.13.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/derpsteb/tink-go/v2 v2.0.0-20231002051717-a808e454eed6 h1:FVii9oXvddz9sFir5TRYjQKrzJLbVD/hibT+SnRSDzg=
github.com/derpsteb/tink-go/v2 v2.0.0-20231002051717-a808e454eed6/go.mod h1:QAbyq9LZncomYnScxlfaHImbV4ieNIe6bnu/Xcqqox4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
Expand Down
47 changes: 47 additions & 0 deletions s3proxy/cmd/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_cross_binary", "go_library")
load("@rules_oci//oci:defs.bzl", "oci_image")
load("@rules_pkg//:pkg.bzl", "pkg_tar")

go_library(
name = "cmd_lib",
srcs = ["main.go"],
importpath = "github.com/edgelesssys/constellation/v2/s3proxy/cmd",
visibility = ["//visibility:private"],
deps = [
"//internal/logger",
"//s3proxy/internal/router",
"@org_uber_go_zap//:zap",
],
)

go_binary(
name = "cmd",
embed = [":cmd_lib"],
visibility = ["//visibility:public"],
)

go_cross_binary(
name = "s3proxy_linux_amd64",
platform = "@io_bazel_rules_go//go/toolchain:linux_amd64",
target = ":cmd",
visibility = ["//visibility:public"],
)

pkg_tar(
name = "layer",
srcs = [
":s3proxy_linux_amd64",
],
mode = "0755",
remap_paths = {"/s3proxy_linux_amd64": "/s3proxy"},
)

oci_image(
name = "s3proxy",
base = "@distroless_static_linux_amd64",
entrypoint = ["/s3proxy"],
tars = [
":layer",
],
visibility = ["//visibility:public"],
)
133 changes: 133 additions & 0 deletions s3proxy/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
Copyright (c) Edgeless Systems GmbH

SPDX-License-Identifier: AGPL-3.0-only
*/

/*
Package main parses command line flags and starts the s3proxy server.
*/
package main

import (
"crypto/tls"
"flag"
"fmt"
"net"
"net/http"

"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/s3proxy/internal/router"
"go.uber.org/zap"
)

const (
// defaultPort is the default port to listen on.
defaultPort = 4433
// defaultIP is the default IP to listen on.
defaultIP = "0.0.0.0"
// defaultRegion is the default AWS region to use.
defaultRegion = "eu-west-1"
// defaultCertLocation is the default location of the TLS certificate.
defaultCertLocation = "/etc/s3proxy/certs"
// defaultLogLevel is the default log level.
defaultLogLevel = 0
)

func main() {
flags, err := parseFlags()
if err != nil {
panic(err)
}

// logLevel can be made a public variable so logging level can be changed dynamically.
// TODO (derpsteb): enable once we are on go 1.21.
// logLevel := new(slog.LevelVar)
// handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel})
// logger := slog.New(handler)
// logLevel.Set(flags.logLevel)

logger := logger.New(logger.JSONLog, logger.VerbosityFromInt(flags.logLevel))

if err := runServer(flags, logger); err != nil {
panic(err)
}
}

func runServer(flags cmdFlags, log *logger.Logger) error {
log.With(zap.String("ip", flags.ip), zap.Int("port", defaultPort), zap.String("region", flags.region)).Infof("listening")

router, err := router.New(flags.region, flags.kmsEndpoint, log)
if err != nil {
return fmt.Errorf("creating router: %w", err)
}

server := http.Server{
Addr: fmt.Sprintf("%s:%d", flags.ip, defaultPort),
Handler: http.HandlerFunc(router.Serve),
// Disable HTTP/2. Serving HTTP/2 will cause some clients to use HTTP/2.
// It seems like AWS S3 does not support HTTP/2.
// Having HTTP/2 enabled will at least cause the aws-sdk-go V1 copy-object operation to fail.
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},
}

// i.e. if TLS is enabled.
if !flags.noTLS {
cert, err := tls.LoadX509KeyPair(flags.certLocation+"/s3proxy.crt", flags.certLocation+"/s3proxy.key")
if err != nil {
return fmt.Errorf("loading TLS certificate: %w", err)
}

server.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
}

// TLSConfig is populated, so we can safely pass empty strings to ListenAndServeTLS.
return server.ListenAndServeTLS("", "")
}

log.Warnf("TLS is disabled")
return server.ListenAndServe()
}

func parseFlags() (cmdFlags, error) {
noTLS := flag.Bool("no-tls", false, "disable TLS and listen on port 80, otherwise listen on 443")
ip := flag.String("ip", defaultIP, "ip to listen on")
region := flag.String("region", defaultRegion, "AWS region in which target bucket is located")
certLocation := flag.String("cert", defaultCertLocation, "location of TLS certificate")
kmsEndpoint := flag.String("kms", "key-service.kube-system:9000", "endpoint of the KMS service to get key encryption keys from")
level := flag.Int("level", defaultLogLevel, "log level")

flag.Parse()

netIP := net.ParseIP(*ip)
if netIP == nil {
return cmdFlags{}, fmt.Errorf("not a valid IPv4 address: %s", *ip)
}

// TODO(derpsteb): enable once we are on go 1.21.
// logLevel := new(slog.Level)
// if err := logLevel.UnmarshalText([]byte(*level)); err != nil {
// return cmdFlags{}, fmt.Errorf("parsing log level: %w", err)
// }

return cmdFlags{
noTLS: *noTLS,
ip: netIP.String(),
region: *region,
certLocation: *certLocation,
kmsEndpoint: *kmsEndpoint,
logLevel: *level,
}, nil
}

type cmdFlags struct {
noTLS bool
ip string
region string
certLocation string
kmsEndpoint string
// TODO(derpsteb): enable once we are on go 1.21.
// logLevel slog.Level
logLevel int
}
63 changes: 63 additions & 0 deletions s3proxy/deploy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Deploying s3proxy

**Caution:** Using s3proxy outside Constellation is insecure as the connection between the key management service (KMS) and s3proxy is protected by Constellation's WireGuard VPN.
The VPN is a feature of Constellation and will not be present by default in other environments.

Disclaimer: the following steps will be automated next.
- Within `constellation/build`: `bazel run //:devbuild`
- Copy the container name displayed for the s3proxy image. Look for the line starting with `[@//bazel/release:s3proxy_push]`.
- Replace the image key in `deployment-s3proxy.yaml` with the image value you just copied. Use the sha256 hash instead of the tag to make sure you use the latest image.
- Replace the `replaceme` values with valid AWS credentials. The s3proxy uses those credentials to access S3.
- Run `kubectl apply -f deployment-s3proxy.yaml`

# Deploying Filestash

Filestash is a demo application that can be used to see s3proxy in action.
To deploy Filestash, first deploy s3proxy as described above.
Then run the below commands:

```sh
$ cat << EOF > "deployment-filestash.yaml"
apiVersion: apps/v1
kind: Deployment
metadata:
name: filestash
spec:
replicas: 1
selector:
matchLabels:
app: filestash
template:
metadata:
labels:
app: filestash
spec:
imagePullSecrets:
- name: regcred
hostAliases:
- ip: $(kubectl get svc s3proxy-service -o=jsonpath='{.spec.clusterIP}')
hostnames:
- "s3.eu-west-1.amazonaws.com"
containers:
- name: filestash
image: machines/filestash:latest
ports:
- containerPort: 8334
volumeMounts:
- name: ca-cert
mountPath: /etc/ssl/certs/kube-ca.crt
subPath: kube-ca.crt
volumes:
- name: ca-cert
secret:
secretName: s3proxy-tls
items:
- key: ca.crt
path: kube-ca.crt
EOF

$ kubectl apply -f deployment-filestash.yaml
```

Afterwards you can use a port forward to access the Filestash pod:
- `kubectl port-forward pod/$(kubectl get pod --selector='app=filestash' -o=jsonpath='{.items[*].metadata.name}') 8443:8443`
Loading