Skip to content

Commit

Permalink
Use clouds.yaml
Browse files Browse the repository at this point in the history
Leverage Gophercloud to parse a clouds.yaml file in the well-known
locations instead of accepting parameters on the command line.
  • Loading branch information
pierreprinetti committed Jun 21, 2024
1 parent 2545814 commit 7cddcb9
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 244 deletions.
38 changes: 27 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,36 @@ All URLs in the OpenStack catalog are rewritten to point to the proxy itself, wh

## Use locally

Download the binary for linux64 on this repository's [release page](https://github.com/pierreprinetti/openstack-mitm/releases) or build it with `go build ./cmd/osp-mitm`.
Build with `go build ./cmd/osp-mitm`.

**Required configuration:**
* **--remote-authurl**: URL of the remote OpenStack Keystone.
* **--proxy-url**: URL the proxy will be reachable at.
`osp-mitm` will parse a `clouds.yaml` file at the known locations, similar to what python-openstackclient does.

**Optional configuration:**
* **--remote-cacert**: path of the local PEM-encoded file containing the CA for the remote certificate.
* **--insecure**: skip TLS verification.
By default the server will listen on localhost on port 13000.

**Configuration:**
* **--url**: URL osp-mitm will be reachable at. Default: `http://locahost:13000`
* **--cert**: path of the local PEM-encoded HTTPS certificate file. Mandatory if the scheme of --url is HTTPS.
* **--key**: path of the local PEM-encoded HTTPS certificate key file. Mandatory if the scheme of --url is HTTPS.
* **-o**: If provided, a new clouds.yaml that points to osp-mitm is created at that location.

## Examples

Local server:
```shell
export OS_CLOUD=openstack
./osp-mitm -o mitm-clouds.yaml
```
```shell
export OS_CLIENT_CONFIG_FILE=./mitm-clouds.yaml
openstack server list
```

Exposing osp-mitm on the network, with HTTPS:

Example:
```shell
./osp-mitm \
--remote-authurl https://openstack.example.com:13000/v3 \
--remote-cacert /var/openstack/cert.pem \
--proxy-url https://localhost:15432'
--url https://myserver.example.com:13000 \
--cert /var/run/osp-cert.pem \
--key /var/run/osp-key.pem' \
-o mitm-clouds.yaml
```
168 changes: 0 additions & 168 deletions cmd/osp-mitm/generate_cert.go

This file was deleted.

118 changes: 55 additions & 63 deletions cmd/osp-mitm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,101 +16,93 @@ package main

import (
"crypto/tls"
"crypto/x509"
"flag"
"log"
"net/http"
"net/url"
"os"
"strings"

"github.com/pierreprinetti/openstack-mitm/pkg/proxy"
)
"github.com/gophercloud/gophercloud/v2/openstack/config/clouds"

const (
defaultPort = "5443"
"github.com/pierreprinetti/openstack-mitm/pkg/cloudout"
"github.com/pierreprinetti/openstack-mitm/pkg/proxy"
)

var (
proxyURLstring = flag.String("proxy-url", "", "The address this proxy will be reachable at")
osAuthURL = flag.String("remote-authurl", "", "OpenStack entrypoint (OS_AUTH_URL)")
osCaCert = flag.String("remote-cacert", "", "OpenStack CA certificate (OS_CACERT)")
insecure = flag.Bool("insecure", false, "Insecure connection to OpenStack")
mitmURL *url.URL
identityEndpoint string
tlsConfig *tls.Config

tlsCertPath string
tlsKeyPath string
)

func init() {
var (
mitmURLstring string
outputCloudPath string
)

flag.StringVar(&mitmURLstring, "url", "http://localhost:13000", "The address this MITM proxy will be reachable at")
flag.StringVar(&tlsCertPath, "cert", "", "Path to the PEM-encoded TLS certificate")
flag.StringVar(&tlsKeyPath, "key", "", "Path to the PEM-encoded TLS certificate private key")
flag.StringVar(&outputCloudPath, "o", "", "Path of the clouds.yaml file that points to this MITM proxy (optional)")
flag.Parse()

var errexit bool
if *proxyURLstring == "" {
errexit = true
log.Print("Missing required parameter: --proxyurl")
var err error
mitmURL, err = url.Parse(mitmURLstring)
if err != nil {
log.Fatalf("Failed to parse the URL (%q): %v", mitmURLstring, err)
}

if *osAuthURL == "" {
log.Print("Missing required parameter: --authurl")
authOptions, endpointOptions, parsedTLSConfig, err := clouds.Parse()
if err != nil {
log.Fatalf("Failed to parse clouds.yaml: %v", err)
}

if errexit {
log.Fatal("Exiting.")
}
}
identityEndpoint = authOptions.IdentityEndpoint
tlsConfig = parsedTLSConfig

func main() {
var proxyURL *url.URL
{
var err error
proxyURL, err = url.Parse(*proxyURLstring)
if outputCloudPath != "" {
f, err := os.Create(outputCloudPath)
if err != nil {
log.Fatal(err)
}

if proxyURL.Host == "" {
log.Fatal("The --proxyurl parameter is invalid. It should be in the form: 'https://host[:port]'.")
log.Fatalf("Failed to create clouds.yaml at the given destination (%q):%v", outputCloudPath, err)
}

if proxyURL.Path != "" {
log.Fatal("The --proxyurl URL should have empty path.")
if err := cloudout.Write(authOptions, endpointOptions, tlsConfig, mitmURL.String(), tlsCertPath, f); err != nil {
log.Fatalf("Failed to encode the output clouds.yaml: %v", err)
}

if proxyURL.Port() == "" {
proxyURL.Host = proxyURL.Hostname() + ":" + defaultPort
if err := f.Close(); err != nil {
log.Fatalf("Failed to finalize the clouds.yaml file: %v", err)
}
log.Printf("clouds.yaml written to %q", outputCloudPath)
}
}

transport := http.DefaultTransport.(*http.Transport)
func main() {
var p http.Handler
{
var err error
transport := http.DefaultTransport.(*http.Transport)
transport.TLSClientConfig = tlsConfig

if caCertPath := *osCaCert; caCertPath != "" {
b, err := os.ReadFile(caCertPath)
p, err = proxy.NewOpenstackProxy(mitmURL.String(), identityEndpoint, transport)
if err != nil {
log.Fatalf("Failed to read the given PEM certificate: %v", err)
log.Fatalf("Failed to build the OpenStack MITM proxy: %v", err)
}
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(b) {
log.Fatal("Failed to parse the given PEM certificate")
}
transport.TLSClientConfig = &tls.Config{RootCAs: certPool}
}

if *insecure {
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: *insecure}
}
listenURL := ":" + mitmURL.Port()

p, err := proxy.NewOpenstackProxy(proxyURL.String(), *osAuthURL, transport)
if err != nil {
panic(err)
}
log.Printf("Proxying to %q", identityEndpoint)
log.Printf("Listening on %q", listenURL)

{
if err := generateCertificate(proxyURL.Hostname()); err != nil {
log.Fatal(err)
}
log.Printf("Certificate correctly generated for %q", proxyURL.Hostname())
switch strings.ToLower(mitmURL.Scheme) {
case "http":
log.Fatal(http.ListenAndServe(listenURL, p))
case "https":
log.Fatal(http.ListenAndServeTLS(listenURL, tlsCertPath, tlsKeyPath, p))
default:
log.Fatalf("Unknown scheme %q", mitmURL.Scheme)
}

log.Printf("Proxying to %q", *osAuthURL)
log.Printf("Listening on %q", proxyURL)

log.Fatal(
http.ListenAndServeTLS(":"+proxyURL.Port(), "cert.pem", "key.pem", p),
)
}
9 changes: 9 additions & 0 deletions cmd/resp/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

import "github.com/pierreprinetti/openstack-mitm/pkg/proxy"

func main() {
clouds.Parse()

Check failure on line 6 in cmd/resp/main.go

View workflow job for this annotation

GitHub Actions / test (1)

undefined: clouds
proxy.NewOpenstackProxy()

Check failure on line 7 in cmd/resp/main.go

View workflow job for this annotation

GitHub Actions / test (1)

not enough arguments in call to proxy.NewOpenstackProxy

}
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@ module github.com/pierreprinetti/openstack-mitm

go 1.22

require github.com/gofrs/uuid/v5 v5.2.0
require (
github.com/gofrs/uuid/v5 v5.2.0
github.com/gophercloud/gophercloud/v2 v2.0.0-rc.3
)

require gopkg.in/yaml.v2 v2.4.0
Loading

0 comments on commit 7cddcb9

Please sign in to comment.