Skip to content
 
 

Repository files navigation


Logo

iCal Filter Proxy

iCal proxy with support for user-defined filtering rules

What's this thing?

Do you have iCal feeds with a bunch of stuff you don't need? Do you want to modify events generated by your rostering system?

iCal Filter Proxy is a simple service for proxying multiple iCal feeds while applying a list of filters to remove or modify events to suit your use case.

Features

  • Proxy multiple calendars
  • Define a list of filters per calendar
  • Match events using basic text and regex conditions
  • Remove or modify events as they are proxied
  • NEW: Generate fully anonymized free/busy feeds (strip all event details except date/time and status)

Built With

Setup

Docker

Docker images are published to Docker Hub. You'll need a config file (see below) mounted into the container at /app/config.yaml.

For example:

docker run -d \
  --name=ical-filter-proxy \
  -v config.yaml:/app/config.yaml \
  -p 8080:8080/tcp \
  --restart unless-stopped \
  yungwood/ical-filter-proxy:latest

You can also adapt the included docker-compose.yaml example.

Kubernetes

You can deploy iCal Filter Proxy using the helm chart from yungwood/helm-charts/ical-filter-proxy.

helm repo add yungwood https://yungwood.github.io/helm-charts/
helm install --name your-release yungwood/ical-filter-proxy

Build from source

You can also build the app and container from source.

# clone this repo
git clone git@github.com:yungwood/ical-filter-proxy.git
cd ical-filter-proxy

# build container image
docker build -t ical-filter-proxy:latest .

Configuration

Calendars and filters are defined in a yaml config file. By default this is config.yaml (use the -config switch to change this). The configuration must define at least one calendar for ical-filter-proxy to start.

Example configuration (with comments):

calendars:
  # basic example
  - name: example # used as slug in URL - e.g. ical-filter-proxy:8080/calendars/example/feed?token=changeme
    publish_name: "My Calendar" # the published name of the calendar - uses upstream value if this line is skipped
    token: "changeme" # optional - token must be used to pull iCal feed if defined
    public: false # optional - must be true if token is blank or not defined
    feed_url: "https://my-upstream-calendar.url/feed.ics" # URL for the upstream iCal feed
    filters: # optional - if no filters defined the upstream calendar is proxied as parsed
      - description: "Remove an event based on a regex"
        remove: true # events matching this filter will be removed
        match: # optional - all events will match if no rules defined
          summary: # match on event summary (title)
            contains: "deleteme" # must contain 'deleteme'
      - description: "Remove descriptions from all events"
        transform: # optional
          description: # modify event description
            remove: true # replace with a blank string

  # free/busy anonymized feed example
  - name: freebusy
    publish_name: "Free/Busy Calendar"
    token: "changeme"
    feed_url: "https://my-upstream-calendar.url/feed.ics"
    freebusy_mode: true # <--- enable anonymization, only date/time and status are kept

  # example: removing noise from an Office 365 calendar
  - name: outlook
    token: "changeme"
    feed_url: "https://outlook.office365.com/owa/calendar/.../reachcalendar.ics"
    filters:
      - description: "Remove canceled events" # canceled events remain with a 'Canceled:' prefix until removed
        remove: true
        match:
          summary:
            prefix: "Canceled: "
      - description: "Remove events without descriptions"
        remove: true
        match:
          description:
            empty: true
      - description: "Remove public holidays"
        remove: true
        match:
          summary:
            regex: ".*[Pp]ublic [Hh]oliday.*"

  # example: cleaning up an OpsGenie feed
  - name: opsgenie
    token: "changeme"
    feed_url: "https://company.app.opsgenie.com/webapi/webcal/getRecentSchedule?webcalToken=..."
    filters:
      - description: "Keep oncall schedule events and fix names"
        match:
          summary:
            contains: "schedule: oncall"
        stop: true # stops processing any more filters
        transform:
          summary:
            replace: "On-Call" # replace the event summary (title)
      - description: "Remove all other events"
        remove: true

Free/Busy Anonymization

If you want to generate a fully anonymized free/busy feed, set freebusy_mode: true for a calendar in your config. This will strip all event details except:

  • Start/end date/time
  • Event status (tentative/confirmed/cancelled)
  • Transparency (free/busy)

All other properties (summary, location, description, attendees, organizer, conference links, attachments, etc.) are removed. This is ideal for sharing availability without exposing any sensitive details.

Filters

Calendar events are filtered using a similar concept to email filtering. A list of filters is defined for each calendar in the config.

Each event parsed from feed_url is evaluated against the filters in sequence.

  • All match rules for a filter must be true to match an event
  • A filter with no match rules will always match
  • When a match is found:
    • if remove is true the event is discarded
    • transform rules are applied to the event
    • if stop is true no more filters are processed
  • If no match is found the event is retained by default

Match conditions

Each filter can specify match conditions against the following event properties:

  • summary (string value)
  • location (string value)
  • description (string value)
  • url (string value)

These match conditions are available for a string value:

  • empty - if true, property must be absent or empty
  • contains - property must contain this value
  • prefix - property must start with this value
  • suffix - property must end with this value
  • regex - property must match the given regular expression (an invalid regex will result in no matches)

Transformations

Transformations can be applied to the following event properties:

  • summary - string value
  • location - string value
  • description - string value
  • url - string value

The following transformations are available for strings:

  • replace - the property is replace with this value
  • remove - if true the property is set to a blank string

Secrets

You can load feed_url and token values from files by specifying the feed_url_file and token_file fields in the calendar configuration. When these fields are set, any values directly provided for feed_url or token are ignored.

For example:

calendars:
  - name: example
    token_file: "/run/secrets/outlook-token"
    feed_url_file: "/run/secrets/outlook-feed"

Security

This project takes security seriously. Please see SECURITY.md for:

  • Supported versions
  • How to report security vulnerabilities
  • Security best practices for deployment
  • Known security considerations

Security Features

  • Constant-time token comparison to prevent timing attacks
  • HTTP client with timeouts to prevent slowloris attacks
  • Request body size limits to prevent memory exhaustion
  • Graceful shutdown handling
  • Security headers on all responses
  • Non-root container execution
  • Automated security scanning via Trivy and Gosec

Testing

Run the test suite:

go test -v -race -coverprofile=coverage.out ./...

View coverage report:

go tool cover -html=coverage.out

Validate your configuration:

./ical-filter-proxy -config config.yaml -validate

Roadmap to 1.0

There are a few more features I would like to add before I call the project "stable" and release version 1.0.

  • Time based event conditions
  • Caching with configurable TTL
  • Prometheus metrics endpoint
  • Testing ✓ Added comprehensive unit tests
  • Security hardening ✓ Implemented multiple security improvements
  • Rate limiting per calendar/token

Development

Running Tests

make test              # Run all tests
make test-coverage     # Run tests with coverage report
make lint              # Run linter checks

Building

make build             # Build binary using Docker
make docker-build      # Build Docker image

Changelog Generation

This project uses git-chglog to automatically generate the CHANGELOG.md from git commit messages. The changelog follows the Keep a Changelog format.

To regenerate the changelog after adding commits:

make changelog

To preview what will be in the next release:

make changelog-next

Commit Message Format:

Please use Conventional Commits format:

  • feat: add new feature - New features
  • fix: fix bug description - Bug fixes
  • docs: update documentation - Documentation changes
  • refactor: refactor code - Code refactoring
  • perf: improve performance - Performance improvements
  • test: add tests - Test additions/changes
  • chore: update dependencies - Maintenance tasks
  • ci: update CI config - CI/CD changes

Example:

fix(calendar): properly remove all PII from anonymized events

- Remove ORGANIZER, ATTENDEE, ATTACH properties
- Remove X- custom properties

Contributing

If you have a suggestion that would make this better, please feel free to open an issue or send a pull request.

License

This project is licensed under the MIT License. See the LICENSE file for details.

Acknowledgments

This project was inspired by darkphnx/ical-filter-proxy. I needed more flexibility with filtering rules and the ability to modify event descriptions... plus I wanted an excuse to finally write something in Go.

About

iCal proxy with support for user-defined filtering rules

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages