authelia/cmd/authelia-scripts/cmd_bootstrap.go

238 lines
6.0 KiB
Go

package main
import (
"fmt"
"os"
"os/exec"
"strings"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/authelia/authelia/v4/internal/utils"
)
// HostEntry represents an entry in /etc/hosts.
type HostEntry struct {
Domain string
IP string
}
var hostEntries = []HostEntry{
// For authelia backend.
{Domain: "authelia.example.com", IP: "192.168.240.50"},
// For common tests.
{Domain: "login.example.com", IP: "192.168.240.100"},
{Domain: "admin.example.com", IP: "192.168.240.100"},
{Domain: "singlefactor.example.com", IP: "192.168.240.100"},
{Domain: "dev.example.com", IP: "192.168.240.100"},
{Domain: "home.example.com", IP: "192.168.240.100"},
{Domain: "mx1.mail.example.com", IP: "192.168.240.100"},
{Domain: "mx2.mail.example.com", IP: "192.168.240.100"},
{Domain: "public.example.com", IP: "192.168.240.100"},
{Domain: "secure.example.com", IP: "192.168.240.100"},
{Domain: "mail.example.com", IP: "192.168.240.100"},
{Domain: "duo.example.com", IP: "192.168.240.100"},
// For Traefik suite.
{Domain: "traefik.example.com", IP: "192.168.240.100"},
// For HAProxy suite.
{Domain: "haproxy.example.com", IP: "192.168.240.100"},
// For testing network ACLs.
{Domain: "proxy-client1.example.com", IP: "192.168.240.201"},
{Domain: "proxy-client2.example.com", IP: "192.168.240.202"},
{Domain: "proxy-client3.example.com", IP: "192.168.240.203"},
// Redis Replicas
{Domain: "redis-node-0.example.com", IP: "192.168.240.110"},
{Domain: "redis-node-1.example.com", IP: "192.168.240.111"},
{Domain: "redis-node-2.example.com", IP: "192.168.240.112"},
// Redis Sentinel Replicas
{Domain: "redis-sentinel-0.example.com", IP: "192.168.240.120"},
{Domain: "redis-sentinel-1.example.com", IP: "192.168.240.121"},
{Domain: "redis-sentinel-2.example.com", IP: "192.168.240.122"},
// Kubernetes dashboard.
{Domain: "kubernetes.example.com", IP: "192.168.240.110"},
// OIDC tester app
{Domain: "oidc.example.com", IP: "192.168.240.100"},
{Domain: "oidc-public.example.com", IP: "192.168.240.100"},
}
func runCommand(cmd string, args ...string) {
command := utils.CommandWithStdout(cmd, args...)
err := command.Run()
if err != nil {
panic(err)
}
}
func checkCommandExist(cmd string, resolutionHint string) {
fmt.Print("Checking if '" + cmd + "' command is installed...")
command := exec.Command("bash", "-c", "command -v "+cmd) //nolint:gosec // Used only in development.
err := command.Run()
if err != nil {
msg := "[ERROR] You must install " + cmd + " on your machine."
if resolutionHint != "" {
msg += fmt.Sprintf(" %s", resolutionHint)
}
log.Fatal(msg)
}
fmt.Println(" OK")
}
func createTemporaryDirectory() {
err := os.MkdirAll("/tmp/authelia", 0755)
if err != nil {
panic(err)
}
}
func bootstrapPrintln(args ...interface{}) {
a := make([]interface{}, 0)
a = append(a, "[BOOTSTRAP]")
a = append(a, args...)
fmt.Println(a...)
}
func shell(cmd string) {
runCommand("bash", "-c", cmd)
}
func prepareHostsFile() {
contentBytes, err := readHostsFile()
if err != nil {
panic(err)
}
lines := strings.Split(string(contentBytes), "\n")
toBeAddedLine := make([]string, 0)
modified := false
for _, entry := range hostEntries {
domainInHostFile := false
for i, line := range lines {
domainFound := strings.Contains(line, entry.Domain)
ipFound := strings.Contains(line, entry.IP)
if domainFound {
domainInHostFile = true
// The IP is not up to date.
if ipFound {
break
} else {
lines[i] = entry.IP + " " + entry.Domain
modified = true
break
}
}
}
if !domainInHostFile {
toBeAddedLine = append(toBeAddedLine, entry.IP+" "+entry.Domain)
}
}
if len(toBeAddedLine) > 0 {
lines = append(lines, toBeAddedLine...)
modified = true
}
fd, err := os.CreateTemp("/tmp/authelia/", "hosts")
if err != nil {
panic(err)
}
_, err = fd.Write([]byte(strings.Join(lines, "\n")))
if err != nil {
panic(err)
}
if modified {
bootstrapPrintln("/etc/hosts needs to be updated")
shell(fmt.Sprintf("cat %s | sudo tee /etc/hosts > /dev/null", fd.Name()))
}
err = fd.Close()
if err != nil {
panic(err)
}
}
// ReadHostsFile reads the hosts file.
func readHostsFile() ([]byte, error) {
bs, err := os.ReadFile("/etc/hosts")
if err != nil {
return nil, err
}
return bs, nil
}
func readVersion(cmd string, args ...string) {
command := exec.Command(cmd, args...)
b, err := command.Output()
if err != nil {
panic(err)
}
fmt.Print(cmd + " => " + string(b))
}
func readVersions() {
readVersion("go", "version")
readVersion("node", "--version")
readVersion("pnpm", "--version")
readVersion("docker", "--version")
readVersion("docker-compose", "version")
}
// Bootstrap bootstrap authelia dev environment.
func Bootstrap(cobraCmd *cobra.Command, args []string) {
bootstrapPrintln("Checking command installation...")
checkCommandExist("node", "Follow installation guidelines from https://nodejs.org/en/download/package-manager/ or download installer from https://nodejs.org/en/download/")
checkCommandExist("pnpm", "Follow installation guidelines from https://pnpm.io/installation")
checkCommandExist("docker", "Follow installation guidelines from https://docs.docker.com/get-docker/")
checkCommandExist("docker-compose", "Follow installation guidelines from https://docs.docker.com/compose/install/")
bootstrapPrintln("Getting versions of tools")
readVersions()
bootstrapPrintln("Checking if GOPATH is set")
goPathFound := false
for _, v := range os.Environ() {
if strings.HasPrefix(v, "GOPATH=") {
goPathFound = true
break
}
}
if !goPathFound {
log.Fatal("GOPATH is not set")
}
createTemporaryDirectory()
bootstrapPrintln("Preparing /etc/hosts to serve subdomains of example.com...")
prepareHostsFile()
fmt.Println()
bootstrapPrintln("Run 'authelia-scripts suites setup Standalone' to start Authelia and visit https://home.example.com:8080.")
bootstrapPrintln("More details at https://github.com/authelia/authelia/blob/master/docs/getting-started.md")
}