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

feat: add support for firewalls #14

Merged
merged 2 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ Tagger is an application that can enforce the presence/absence of API tags in bu
| NodeBalancers | `nodebalancers:read_write` |
| Domains | `domains:read_write` |
| LKEClusters | `lke:read_write` |
| Firewalls | `firewall:read_write` |

## Motivation

Tools like Terraform/Pulumi that are capable of programmatically managing all aspects of API resources are great -- but if there is already a large amount of infrastructure deployed (and the infrastructure isn't suitable to directly import to something like Terraform state as-is), it can be difficult to manage tags across API resources.

API tags provide a powerful and flexible way to dynamically annotate infrastructure. With tools like [Prometheus](https://prometheus.io), you can even discover monitoring targets using [Linode Service Discovery](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#linode_sd_config) based on API tags.

So it's helpful to be able to manage tags on Linode APIv4 resources en-masse -- but how can that be done in an idempotent and consistent way? This is where `tagger` comes into play. With tagger, you write a configuration file defining a list of tag rules for each of the various Linode APIv4 taggable objects (instances, domains, nodebalancers, volumes, LKE clusters).
So it's helpful to be able to manage tags on Linode APIv4 resources en-masse -- but how can that be done in an idempotent and consistent way? This is where `tagger` comes into play. With tagger, you write a configuration file defining a list of tag rules for each of the various Linode APIv4 taggable objects (instances, domains, nodebalancers, volumes, LKE clusters, firewalls).

Each rule is a regex to be matched against the resource's human-readable label, and a list of tags that should be enforced as either `present` or `absent` on the resource. `tagger` is idempotent and doesn't update resources unless required, and can be run in `--dry-run` mode to see what changes are waiting. JSON output is provided as well with the `--json` flag for easy manipulation/inspection of the diffs and integration with other tools. Full help text:

Expand Down
7 changes: 7 additions & 0 deletions packaging/etc/tagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,10 @@ tagger:
absent:
- tagger_absent_1
- tagger_absent_2
firewalls:
- regex: '.+'
tags:
present:
- tagger_managed_1
absent:
- tagger_absent_1
64 changes: 64 additions & 0 deletions tagger.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type TaggerConfig struct {
NodeBalancers []TagRule `yaml:"nodebalancers"`
Domains []TagRule `yaml:"domains"`
LKEClusters []TagRule `yaml:"lke_clusters"`
Firewalls []TagRule `yaml:"firewalls"`
}

// LinodeObjectCollection holds a slice of each type of
Expand All @@ -52,6 +53,7 @@ type LinodeObjectCollection struct {
NodeBalancers []linodego.NodeBalancer
Domains []linodego.Domain
LKEClusters []linodego.LKECluster
Firewalls []linodego.Firewall
}

// LinodeObjectTags holds an object's ID, as well as the desired set of tags that it should have.
Expand All @@ -67,6 +69,7 @@ type LinodeObjectDesiredTagsCollection struct {
NodeBalancers []LinodeObjectDesiredTags
LKEClusters []LinodeObjectDesiredTags
Volumes []LinodeObjectDesiredTags
Firewalls []LinodeObjectDesiredTags
}

// Diff contains a list of tags that have been added/removed from a given
Expand All @@ -88,6 +91,7 @@ type LinodeObjectCollectionDiff struct {
NodeBalancers []LinodeObjectDiff `json:"nodebalancers"`
LKEClusters []LinodeObjectDiff `json:"lkeclusters"`
Volumes []LinodeObjectDiff `json:"volumes"`
Firewalls []LinodeObjectDiff `json:"firewalls"`
}

func newLinodeClient() linodego.Client {
Expand Down Expand Up @@ -192,6 +196,7 @@ func getLinodeObjectCollectionDiff(loc LinodeObjectCollection, desiredTags Linod
})
}
}

// nodebalancers
for _, old := range desiredTags.NodeBalancers {
for _, cur := range loc.NodeBalancers {
Expand All @@ -207,6 +212,21 @@ func getLinodeObjectCollectionDiff(loc LinodeObjectCollection, desiredTags Linod
}
}

// firewalls
for _, old := range desiredTags.Firewalls {
for _, cur := range loc.Firewalls {
if old.ID != cur.ID {
continue
}

diff.Firewalls = append(diff.Firewalls, LinodeObjectDiff{
ID: cur.ID,
Label: cur.Label,
Diff: getTagDiff(old.Old, cur.Tags),
})
}
}

return diff
}

Expand Down Expand Up @@ -388,6 +408,28 @@ func compareAllObjectTagsAgainstConfig(loc LinodeObjectCollection, config Tagger
}
}

// firewalls
for _, firewall := range loc.Firewalls {
currTags := firewall.Tags
newTags, found := getNewTags(firewall.Label, currTags, config.Firewalls)

sort.Strings(currTags)
sort.Strings(newTags)
if found && !slices.Equal(currTags, newTags) {
desiredNewTags.Firewalls = append(desiredNewTags.Firewalls, LinodeObjectDesiredTags{
ID: firewall.ID,
Old: currTags,
New: newTags,
})

diff.Firewalls = append(diff.Firewalls, LinodeObjectDiff{
ID: firewall.ID,
Label: firewall.Label,
Diff: getTagDiff(currTags, newTags),
})
}
}

return desiredNewTags, diff
}

Expand Down Expand Up @@ -464,6 +506,20 @@ func updateAllObjectTags(ctx context.Context, client linodego.Client, desiredTag
loc.Volumes = append(loc.Volumes, *updatedVolume)
}

// update firewall tags
for _, f := range desiredTags.Firewalls {
updatedFirewall, err := client.UpdateFirewall(ctx, f.ID, linodego.FirewallUpdateOptions{Tags: &f.New})
if err != nil {
log.WithFields(log.Fields{
"id": f.ID,
"error": err,
}).Error("Failed to update firewall tags")
return loc, err
}

loc.Firewalls = append(loc.Firewalls, *updatedFirewall)
}

return loc, nil
}

Expand Down Expand Up @@ -638,6 +694,14 @@ func tagger(config TaggerConfig) {
loc.LKEClusters = lkeclusters
}

if len(config.Firewalls) > 0 {
firewalls, err := client.ListFirewalls(ctx, nil)
if err != nil {
log.WithFields(log.Fields{"err": err}).Fatal("Failed to list firewalls")
}
loc.Firewalls = firewalls
}

log.Info("Checking linode object tags against config file")
desiredTags, tagDiff := compareAllObjectTagsAgainstConfig(loc, config)

Expand Down
Loading