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

Add support for YAML configuration files #6

Merged
merged 4 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ normally support App Connectors.

## Example

Here is a sample config file, in JSON format:
Create a configuration file in either JSON or YAML format. Here’s an example:

`config.json`
```json
{
"routes": [
Expand All @@ -44,12 +45,25 @@ Here is a sample config file, in JSON format:
"hostRoutes": [
"github.com",
"private-app.example.com"
],
],̌
dhyanio marked this conversation as resolved.
Show resolved Hide resolved
dhyanio marked this conversation as resolved.
Show resolved Hide resolved
"awsManagedPrefixLists": [
"pl-02761f4a40454a3c9"
]
}
```
or `config.yaml`
```yaml
routes:
- "172.16.0.0/22"
- "192.168.0.0/24"
hostRoutes:
- "special-hostname1.example"
- "special-hostname2.example"
awsManagedPrefixLists:
- "pl-02761f4a40454a3c9"
extraArgs:
- "--webclient"
```

Run tailscale-manager:

Expand Down
26 changes: 9 additions & 17 deletions src/TailscaleManager.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- | Tailscale Routes Manager
{-# LANGUAGE QuasiQuotes #-}

dhyanio marked this conversation as resolved.
Show resolved Hide resolved
module TailscaleManager where

Expand All @@ -14,7 +14,6 @@ import Prettyprinter (Doc)
import System.Log.Logger
import System.Process (callProcess, showCommandForUser)
import Text.RawString.QQ (r)

import TailscaleManager.Config
import TailscaleManager.Discovery.AWSManagedPrefixList (resolveAllPrefixLists)
import TailscaleManager.Discovery.DNS (resolveHostnamesToRoutes)
Expand All @@ -40,7 +39,7 @@ helpText = [r|Tailscale routes manager
Dynamically resolves a list of hostRoutes to IP addresses, then tells tailscale
to advertise them as /32 routes along with any normal CIDR routes.

Config file example:
Config file example (JSON):

{
"routes": [
Expand All @@ -49,7 +48,7 @@ Config file example:
],
"hostRoutes": [
"special-hostname1.example",
"special-hostname2.example",
"special-hostname2.example"
],
"awsManagedPrefixLists": [
"pl-02761f4a40454a3c9"
Expand All @@ -60,7 +59,7 @@ Config file example:
tsManagerOptions :: Parser TailscaleManagerOptions
tsManagerOptions =
TailscaleManagerOptions
<$> argument str (metavar "<configfile.json>")
<$> argument str (metavar "<configfile>")
<*> switch (long "dryrun"
<> help "Dryrun mode")
<*> strOption (long "tailscale"
Expand Down Expand Up @@ -120,7 +119,7 @@ runOnce options prevRoutes = do
logL logger DEBUG ("Sleeping for " ++ show (interval options) ++ " seconds")
threadDelay (interval options * 1000000) -- microseconds

config <- loadConfig (configFile options)
config <- TailscaleManager.Config.loadConfig (configFile options)
newRoutes <- generateRoutes config
dhyanio marked this conversation as resolved.
Show resolved Hide resolved

logDiff prevRoutes newRoutes
Expand All @@ -137,7 +136,7 @@ runOnce options prevRoutes = do
logDelay
return prevRoutes

-- |Emit a log message describing the difference between old and new route sets.
-- | Emit a log message describing the difference between old and new route sets.
logDiff :: Set IPRange -> Set IPRange -> IO ()
logDiff prevRoutes newRoutes = do
logger <- myLogger
Expand All @@ -154,18 +153,11 @@ logDiff prevRoutes newRoutes = do
routesToRemove = S.difference prevRoutes newRoutes
routesUnchanged = S.intersection prevRoutes newRoutes

-- |Compute how much the smaller the new set is vs old.
--
-- >>> shrinkRatio ["1.1.1.1/32", "2.2.2.2/32"] ["1.1.1.1/32"]
-- 0.5
shrinkRatio :: Foldable t
=> t a -- ^ Old set
-> t a -- ^ New set
-> Double -- ^ Shrink ratio
-- | Compute how much smaller the new set is vs old.
shrinkRatio :: Foldable t => t a -> t a -> Double
shrinkRatio old new = 1 - (1 / (fromIntegral (length old) / fromIntegral (length new)))
dhyanio marked this conversation as resolved.
Show resolved Hide resolved

-- |Do all the hostname resolution and concat the results with static routes from
-- the config.
-- | Generate routes based on config, resolving hostnames and AWS-managed prefix lists.
generateRoutes :: TSConfig -> IO (Set IPRange)
generateRoutes config = do
hostRoutes <- resolveHostnamesToRoutes (tsHostRoutes config)
Expand Down
31 changes: 27 additions & 4 deletions src/TailscaleManager/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
module TailscaleManager.Config where

import Data.Aeson
import Data.Aeson (eitherDecodeFileStrict)
import Data.Aeson.IP ()
import Data.ByteString.Lazy qualified as LB
import Data.IP (IPRange)
import Data.Maybe (fromMaybe)
import Data.Text

-- |Parse our config file. May throw AesonException on failure.
loadConfig :: FilePath -> IO TSConfig
loadConfig fp = LB.readFile fp >>= throwDecode
import System.FilePath (takeExtension)
import Data.Yaml (decodeFileEither)

-- |Config file schema
data TSConfig
Expand All @@ -35,3 +34,27 @@ instance FromJSON TSConfig where
, tsExtraArgs = fromMaybe [] extraArgs
, tsAWSManagedPrefixLists = fromMaybe [] awsManagedPrefixLists
})

-- | Load configuration from a file, detecting format based on file extension.
loadConfig :: FilePath -> IO TSConfig
loadConfig path = do
case takeExtension path of
".json" -> loadConfigFromJSON path
".yaml" -> loadConfigFromYAML path
_ -> error "Unsupported file format. Please use .json or .yaml."

-- | Load configuration from a JSON file.
loadConfigFromJSON :: FilePath -> IO TSConfig
loadConfigFromJSON path = do
result <- eitherDecodeFileStrict path
case result of
Left err -> error $ "Failed to parse JSON config: " ++ err
Right config -> return config

-- | Load configuration from a YAML file.
loadConfigFromYAML :: FilePath -> IO TSConfig
loadConfigFromYAML path = do
result <- decodeFileEither path
case result of
Left err -> error $ "Failed to parse YAML config: " ++ show err
Right config -> return config
2 changes: 2 additions & 0 deletions tailscale-manager.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ common deps
, protolude ^>= 0.3.4
, raw-strings-qq ^>= 1.1
, text ^>= 2.0.2
, filepath ^>= 1.4.2
, yaml ^>= 0.11.8

executable tailscale-manager
import: warnings, deps
Expand Down
Loading