Skip to content

Commit

Permalink
Custom config directory and automatic authentication
Browse files Browse the repository at this point in the history
This commit adds two new provider features:

1. Ability to specify a custom config_dir. This is useful for keeping
LXD configurations in a shared directory.

2. Ability to authenticate to an LXD server. This provides the ability
to not have to authenticate to a new LXD server out-of-band of Terraform.
  • Loading branch information
jtopjian authored and sl1pm4t committed Apr 5, 2017
1 parent a4e398b commit 71d7876
Show file tree
Hide file tree
Showing 5 changed files with 326 additions and 8 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ To generate these files and store them in the LXD daemon, follow these [steps](h
provider "lxd" {
scheme = "https"
address = "10.1.1.8"
remote = "lxd-server"
}
```

Expand Down Expand Up @@ -234,10 +235,12 @@ _note_: `local` and `remote` accept both IPv4 and IPv6 addresses.
* `scheme` - *Optional* - `https` or `unix`. Defaults to `unix`.
* `port` - *Optional* - `https` scheme only - The port on which the LXD daemon is listening. Defaults to 8443.
* `remote` - *Optional* - Name of the remote LXD as it exists in the local lxc config. Defaults to `local`.
* `remote_password` - *Optional* - Password of the remote LXD server.
* `config_dir` - *Optional* - Directory path to client LXD configuration and certs. Defaults to `$HOME/.config/lxc`.

### Resources

The following resources currently exist:
The following resources are currently available:

* `lxd_container` - Creates and manages a Container
* `lxd_network` - Creates and manages a Network
Expand Down
80 changes: 73 additions & 7 deletions lxd/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/lxc/lxd"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
)

type LxdProvider struct {
Expand Down Expand Up @@ -51,6 +52,23 @@ func Provider() terraform.ResourceProvider {
Description: descriptions["lxd_remote"],
DefaultFunc: schema.EnvDefaultFunc("LXD_REMOTE", "local"),
},

"remote_password": &schema.Schema{
Type: schema.TypeString,
Sensitive: true,
Optional: true,
Description: descriptions["lxc_remote_password"],
DefaultFunc: schema.EnvDefaultFunc("LXD_REMOTE_PASSWORD", ""),
},

"config_dir": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: descriptions["lxd_config_dir"],
DefaultFunc: func() (interface{}, error) {
return os.ExpandEnv("$HOME/.config/lxc"), nil
},
},
},

ResourcesMap: map[string]*schema.Resource{
Expand All @@ -67,10 +85,12 @@ var descriptions map[string]string

func init() {
descriptions = map[string]string{
"lxd_address": "The FQDN or IP where the LXD daemon can be contacted. (default = /var/lib/lxd/unix.socket)",
"lxd_scheme": "unix or https (default = unix)",
"lxd_port": "Port LXD Daemon is listening on (default 8443).",
"lxd_remote": "Name of the LXD remote. Required when lxd_scheme set to https, to enable locating server certificate.",
"lxd_address": "The FQDN or IP where the LXD daemon can be contacted. (default = /var/lib/lxd/unix.socket)",
"lxd_scheme": "unix or https (default = unix)",
"lxd_port": "Port LXD Daemon is listening on (default 8443).",
"lxd_remote": "Name of the LXD remote. Required when lxd_scheme set to https, to enable locating server certificate.",
"lxd_remote_password": "The password for the remote.",
"lxd_config_dir": "The directory to look for existing LXD configuration (default = $HOME/.config/lxc).",
}
}

Expand All @@ -91,7 +111,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {

// build LXD config
config := lxd.Config{
ConfigDir: os.ExpandEnv("$HOME/.config/lxc"),
ConfigDir: d.Get("config_dir").(string),
Remotes: make(map[string]lxd.RemoteConfig),
}
config.Remotes[remote] = lxd.RemoteConfig{Addr: daemon_addr}
Expand All @@ -107,8 +127,11 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
}
serverCertf := config.ServerCertPath(remote)
if !shared.PathExists(serverCertf) {
err := fmt.Errorf("Server certificate not found:\n\t%s", serverCertf)
return nil, err
// If the server certificate was not found, try to add the remote.
err := addRemote(d, &config)
if err != nil {
return nil, err
}
}
}

Expand Down Expand Up @@ -137,3 +160,46 @@ func validateClient(client *lxd.Client) error {
}
return nil
}

func addRemote(d *schema.ResourceData, config *lxd.Config) error {
// First, validate the client.
remote := d.Get("remote").(string)
c, err := lxd.NewClient(config, remote)
if err != nil {
return err
}

// Check if the client is valid.
// Right now, this assumes PKI is in place by previous provisioning
// of client, server, and CA certificates.
// The LXC command-line client would prompt to accept a certificate
// if PKI was not in place.
_, err = c.GetServerConfig()
if err != nil {
return err
}

// If the config is valid, check and see if the client is already trusted
if c.AmTrusted() {
log.Printf("[DEBUG] LXC client is trusted with %s", remote)
return nil
}

// If not trusted, try to authenticate
body := shared.Jmap{
"type": "client",
"password": d.Get("remote_password").(string),
}

log.Printf("[DEBUG] Attempting to authenticate with remote %s", remote)
_, err = clientDoUpdateMethod(c, "POST", "certificates", body, api.SyncResponse)

if err != nil {
log.Printf("[DEBUG] Failed to authenticate with remote %s", remote)
return err
}

log.Printf("[DEBUG] Successfully authenticated with remote %s", remote)

return nil
}
66 changes: 66 additions & 0 deletions lxd/shared.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package lxd

import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"

"github.com/lxc/lxd"
"github.com/lxc/lxd/shared/api"
"github.com/lxc/lxd/shared/version"
)

func resourceLxdConfigMap(c interface{}) map[string]string {
Expand Down Expand Up @@ -56,3 +64,61 @@ func resourceLxdValidateDeviceType(v interface{}, k string) (ws []string, errors

return
}

// The following re-implements private LXC client functions
func clientURL(baseURL string, elem ...string) string {
// Normalize the URL
path := strings.Join(elem, "/")
entries := []string{}
fields := strings.Split(path, "/")
for i, entry := range fields {
if entry == "" && i+1 < len(fields) {
continue
}

entries = append(entries, entry)
}
path = strings.Join(entries, "/")

// Assemble the final URL
uri := baseURL + "/" + path

// Aliases may contain a trailing slash
if strings.HasPrefix(path, "1.0/images/aliases") {
return uri
}

// File paths may contain a trailing slash
if strings.Contains(path, "?") {
return uri
}

// Nothing else should contain a trailing slash
return strings.TrimSuffix(uri, "/")
}

func clientDoUpdateMethod(client *lxd.Client, method string, base string, args interface{}, rtype api.ResponseType) (*api.Response, error) {
uri := clientURL(client.BaseURL, version.APIVersion, base)

buf := bytes.Buffer{}
err := json.NewEncoder(&buf).Encode(args)
if err != nil {
return nil, err
}

log.Printf("[DEBUG] %s %s to %s", method, buf.String(), uri)

req, err := http.NewRequest(method, uri, &buf)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", version.UserAgent)
req.Header.Set("Content-Type", "application/json")

resp, err := client.Http.Do(req)
if err != nil {
return nil, err
}

return lxd.HoistResponse(resp, rtype)
}
171 changes: 171 additions & 0 deletions lxd/test-fixtures/pki/1_infra/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
resource "tls_private_key" "ca" {
algorithm = "RSA"

# Private SSL key
provisioner "local-exec" {
command = "echo \"${tls_private_key.ca.private_key_pem}\" > ca_key.pem"
}
}

resource "tls_self_signed_cert" "ca" {
key_algorithm = "RSA"
private_key_pem = "${tls_private_key.ca.private_key_pem}"

subject {
common_name = "example"
organization = "example.com"
}

allowed_uses = [
"key_encipherment",
"cert_signing",
"server_auth",
"client_auth"
]

validity_period_hours = 24000
early_renewal_hours = 720
is_ca_certificate = true

# Certs
provisioner "local-exec" {
command = "echo \"${tls_self_signed_cert.ca.cert_pem}\" > ca_cert.pem"
}

provisioner "local-exec" {
command = "echo \"${tls_self_signed_cert.ca.cert_pem}\" > ~/.config/lxc/client.ca"
}
}

resource "tls_private_key" "local" {
algorithm = "RSA"

# Private SSL key
provisioner "local-exec" {
command = "echo \"${tls_private_key.local.private_key_pem}\" > client_key.pem"
}

provisioner "local-exec" {
command = "echo \"${tls_private_key.local.private_key_pem}\" > ~/.config/lxc/client.key"
}

# OpenSSH key
provisioner "local-exec" {
command = "echo '${tls_private_key.local.public_key_openssh}' > id_rsa.pub"
}

provisioner "local-exec" {
command = "echo '${tls_private_key.local.private_key_pem}' > id_rsa"
}
}

resource "tls_cert_request" "local" {
key_algorithm = "RSA"
private_key_pem = "${tls_private_key.local.private_key_pem}"

dns_names = ["client.example.com"]
subject {
common_name = "client"
}

}

resource "tls_locally_signed_cert" "local" {
cert_request_pem = "${tls_cert_request.local.cert_request_pem}"

ca_key_algorithm = "RSA"
ca_private_key_pem = "${tls_private_key.ca.private_key_pem}"
ca_cert_pem = "${tls_self_signed_cert.ca.cert_pem}"

validity_period_hours = 24000

allowed_uses = [
"key_encipherment",
"digital_signature",
"server_auth",
"client_auth",
]

provisioner "local-exec" {
command = "echo \"${tls_locally_signed_cert.local.cert_pem}\" > client_cert.pem"
}

provisioner "local-exec" {
command = "echo \"${tls_locally_signed_cert.local.cert_pem}\" > ~/.config/lxc/client.crt"
}
}

resource "openstack_compute_keypair_v2" "lxd" {
name = "lxd"
public_key = "${tls_private_key.local.public_key_openssh}"
}

resource "openstack_compute_instance_v2" "lxd" {
name = "lxd"
image_name = "Ubuntu 16.04"
flavor_name = "m1.medium"
key_pair = "${openstack_compute_keypair_v2.lxd.name}"
security_groups = ["AllowAll"]
user_data = "#cloud-config\ndisable_root: false"
}

resource "null_resource" "lxd" {
connection {
user = "root"
private_key = "${tls_private_key.local.public_key_openssh}"
host = "${openstack_compute_instance_v2.lxd.access_ip_v6}"
}

provisioner "remote-exec" {
inline = [
"apt-add-repository -y ppa:ubuntu-lxc/stable",
"apt-get update -qq",
"apt-get install -y lxd",
"lxc config set core.https_address [::]",
"lxc config set core.trust_password password",
"lxc storage create default dir",
"lxc profile device add default root disk path=/ pool=default",
"echo '${tls_self_signed_cert.ca.cert_pem}' | sudo tee /var/lib/lxd/server.ca",
"echo '${tls_locally_signed_cert.lxd.cert_pem}' | sudo tee /var/lib/lxd/server.crt",
"echo '${tls_self_signed_cert.ca.cert_pem}' | sudo tee -a /var/lib/lxd/server.crt",
"echo '${tls_private_key.lxd.private_key_pem}' | sudo tee /var/lib/lxd/server.key",
"systemctl restart lxd",
"lxc image copy ubuntu:16.04 local: --alias ubuntu",
]
}
}

resource "tls_private_key" "lxd" {
algorithm = "RSA"
}

resource "tls_cert_request" "lxd" {
key_algorithm = "RSA"
private_key_pem = "${tls_private_key.lxd.private_key_pem}"
ip_addresses = ["${replace(openstack_compute_instance_v2.lxd.access_ip_v6, "/[][]/", "")}"]

subject {
common_name = "${openstack_compute_instance_v2.lxd.name}"
}
}

resource "tls_locally_signed_cert" "lxd" {
cert_request_pem = "${tls_cert_request.lxd.cert_request_pem}"

ca_key_algorithm = "RSA"
ca_private_key_pem = "${tls_private_key.ca.private_key_pem}"
ca_cert_pem = "${tls_self_signed_cert.ca.cert_pem}"

validity_period_hours = 24000
early_renewal_hours = 720

allowed_uses = [
"key_encipherment",
"server_auth",
"client_auth"
]
}

output "hostname" {
value = "${openstack_compute_instance_v2.lxd.access_ip_v6}"
}
Loading

0 comments on commit 71d7876

Please sign in to comment.