packages feed

salmon-ops-recipes-0.1.0.0: src/SreBox/DNSRegistration.hs

{-# LANGUAGE DeriveGeneric #-}

module SreBox.DNSRegistration where

import Data.Aeson (FromJSON, ToJSON)
import Data.Text (Text)
import qualified Data.Text as Text
import GHC.Generics (Generic)

import qualified Salmon.Builtin.CommandLine as CLI
import Salmon.Builtin.Extension
import qualified Salmon.Builtin.Nodes.Binary as Binary
import qualified Salmon.Builtin.Nodes.CronTask as CronTask
import qualified Salmon.Builtin.Nodes.Debian.OS as Debian
import qualified Salmon.Builtin.Nodes.Filesystem as FS
import qualified Salmon.Builtin.Nodes.Rsync as Rsync
import qualified Salmon.Builtin.Nodes.Self as Self
import qualified Salmon.Builtin.Nodes.Ssh as Ssh
import Salmon.Op.OpGraph (inject)
import Salmon.Op.Track
import Salmon.Reporter
import SreBox.MicroDNS (DNSName, MicroDNSConfig (..), selfSignedCert, sharedToken)
import qualified SreBox.MicroDNS as MicroDNS

-------------------------------------------------------------------------------
data Report
    = UploadToken !Rsync.Report
    | UploadPEM !Rsync.Report
    | SelfSign !MicroDNS.Report
    | MakeToken !MicroDNS.Report
    | UploadSelf !Self.Report
    | CallSelf !Self.Report
    deriving (Show)

-------------------------------------------------------------------------------

data RegisteredMachineConfig
    = RegisteredMachineConfig
    { registration_machineName :: DNSName
    , registration_cfg_local_token_path :: FilePath
    , --
      registration_cfg_task_script :: FilePath
    , registration_cfg_task_data_path_token :: FilePath
    , registration_cfg_task_data_path_pem :: FilePath
    }

setupRegistration ::
    forall directive.
    (FromJSON directive, ToJSON directive) =>
    Reporter Report ->
    Track' Ssh.Remote ->
    Track' directive ->
    Self.SelfPath ->
    Self.Remote ->
    MicroDNSConfig ->
    RegisteredMachineConfig ->
    (RegisteredMachineSetup -> directive) ->
    Op
setupRegistration r mkRemote simulate selfpath selfRemote dns cfg toSpec =
    op "registration" (deps [trackedGraph remoteRegistration, uploadPem, uploadToken]) id
  where
    rsyncRemote :: Rsync.Remote
    rsyncRemote = (\(Self.Remote a b) -> Rsync.Remote a b) selfRemote

    tmpTokenPath :: FilePath
    tmpTokenPath = "tmp/" <> Text.unpack cfg.registration_machineName <> "-registration.token"

    uploadToken =
        Rsync.sendFile (contramap UploadToken r) Debian.rsync (FS.Generated mkToken cfg.registration_cfg_local_token_path) rsyncRemote tmpTokenPath

    tmpPemPath :: FilePath
    tmpPemPath = "tmp/registration.pem"

    uploadPem =
        Rsync.sendFile (contramap UploadPEM r) Debian.rsync (FS.Generated (selfSignedCert (contramap SelfSign r) dns) dns.microdns_cfg_pemPath) rsyncRemote tmpPemPath

    mkToken :: Track' FilePath
    mkToken = Track $ \tokenPath ->
        sharedToken (contramap MakeToken r) dns.microdns_cfg_secretPath cfg.registration_machineName tokenPath

    self = Self.uploadSelf (contramap UploadSelf r) "tmp" selfRemote selfpath
    remoteRegistration = self `bindTracked` \ref -> Self.callSelfAsSudo (contramap CallSelf r) mkRemote ref simulate CLI.Up (toSpec regSetup)

    regSetup =
        RegisteredMachineSetup
            cfg.registration_machineName
            tmpTokenPath
            tmpPemPath
            (cfg.registration_cfg_task_script)
            (cfg.registration_cfg_task_data_path_token)
            (cfg.registration_cfg_task_data_path_pem)

data RegisteredMachineSetup
    = RegisteredMachineSetup
    { registration_name :: DNSName
    , registration_token_tmppath :: FilePath
    , registration_pem_tmppath :: FilePath
    , -- where to copy to
      registration_script_path :: FilePath
    , registration_token_path :: FilePath
    , registration_pem_path :: FilePath
    }
    deriving (Generic)

instance FromJSON RegisteredMachineSetup
instance ToJSON RegisteredMachineSetup

registerMachine :: RegisteredMachineSetup -> Op
registerMachine setup =
    op "enrolled-machine" (deps [autoregister, movetoken, movepem, Binary.justInstall Debian.curl]) id
  where
    tokenPath = setup.registration_token_path
    pemPath = setup.registration_pem_path
    taskScript = setup.registration_script_path

    movetoken :: Op
    movetoken =
        FS.fileCopy setup.registration_token_tmppath tokenPath

    movepem :: Op
    movepem =
        FS.fileCopy setup.registration_pem_tmppath pemPath

    scriptcontents :: Text
    scriptcontents = autoregisterscript setup

    autoregister :: Op
    autoregister =
        let
            task =
                CronTask.CronTask
                    ("register-dns_" <> setup.registration_name)
                    "root"
                    CronTask.everyMinute
                    "bash"
                    [ Text.pack taskScript
                    , Text.pack tokenPath
                    , setup.registration_name
                    , Text.pack pemPath
                    ]
         in
            CronTask.crontask ignoreTrack task `inject` FS.filecontents (FS.FileContents taskScript scriptcontents)

autoregisterscript :: RegisteredMachineSetup -> Text
autoregisterscript setup =
    Text.unlines
        [ "#!/bin/bash"
        , ""
        , "hmacpath=$1"
        , "regname=$2"
        , "pempath=$3"
        , ""
        , "hmac=`cat ${hmacpath}`"
        , "curl -XPOST \\"
        , "  --cacert \"${pempath}\"\\"
        , "  -H \"x-microdns-hmac: ${hmac}\" \\"
        , "  \"https://box.dicioccio.fr:65432/register/auto/${regname}\""
        ]