Skip to content
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
62 changes: 52 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,17 @@ Each site node identifies itself via the `SITE_SUBDOMAIN` environment variable,

### Authentication

Upright supports OpenID Connect for authentication (Logto, Keycloak, Duo, Okta, etc.):
#### Static Credentials

Upright uses static credentials by default with username `admin` and password `upright`.

> [!WARNING]
> Change the default password before deploying to production by setting the `ADMIN_PASSWORD` environment variable.


#### OpenID Connect

For production environments, Upright supports OpenID Connect (Logto, Keycloak, Duo, Okta, etc.):

```ruby
# config/initializers/upright.rb
Expand All @@ -131,14 +141,6 @@ Upright.configure do |config|
end
```

By default authentication is disabled but this is suitable for internal networks only:

```ruby
Upright.configure do |config|
config.auth_provider = nil
end
```

## Defining Probes

### HTTP Probes
Expand Down Expand Up @@ -402,7 +404,47 @@ Upright.configure do |config|
end
```

### Testing
## Local Development

### Setup

```bash
bin/setup
```

This installs dependencies, prepares the database, and starts the dev server.

### Running Services

Start supporting Docker services (Playwright server, etc.):

```bash
bin/services
```

### Running the Server

```bash
bin/dev
```

Visit http://app.upright.localhost:3000 and sign in with:
- **Username**: `admin`
- **Password**: `upright` (or value of `ADMIN_PASSWORD` env var)

### Testing Playwright Probes

Run probes with a visible browser window:

```bash
LOCAL_PLAYWRIGHT=1 bin/rails console
```

```ruby
Probes::Playwright::MyServiceAuthProbe.check
```

### Running Tests

```bash
bin/rails test
Expand Down
4 changes: 1 addition & 3 deletions app/controllers/concerns/upright/authentication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ module Upright::Authentication

private
def authenticate_user
if Upright.configuration.auth_provider.nil?
Upright::Current.user = Upright::User.new(email: "anonymous", name: "Anonymous")
elsif session[:user_info].present?
if session[:user_info].present?
Upright::Current.user = Upright::User.new(session[:user_info])
else
redirect_to engine_routes.new_admin_session_url(default_url_options.merge(subdomain: Upright.configuration.admin_subdomain)), allow_other_host: true
Expand Down
1 change: 1 addition & 0 deletions app/controllers/upright/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class Upright::SessionsController < Upright::ApplicationController
skip_before_action :authenticate_user, only: [ :new, :create ]
skip_forgery_protection only: :create

before_action :ensure_not_signed_in, only: [ :new, :create ]

Expand Down
4 changes: 4 additions & 0 deletions bin/dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -eu
cd "$(dirname "${BASH_SOURCE[0]}")/../test/dummy"
exec bin/dev "$@"
4 changes: 4 additions & 0 deletions bin/services
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -eu
cd "$(dirname "${BASH_SOURCE[0]}")/../test/dummy"
exec bin/services "$@"
4 changes: 4 additions & 0 deletions bin/setup
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -eu
cd "$(dirname "${BASH_SOURCE[0]}")/../test/dummy"
exec bin/setup "$@"
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
root "sites#index", as: :admin_root

resource :session, only: [ :new, :create ], as: :admin_session
get "auth/:provider/callback", to: "sessions#create", as: :auth_callback
match "auth/:provider/callback", to: "sessions#create", as: :auth_callback, via: [ :get, :post ]

# Dashboards
scope :dashboards, as: :dashboard do
Expand Down
7 changes: 4 additions & 3 deletions lib/generators/upright/install/install_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ class InstallGenerator < Rails::Generators::Base

desc "Install Upright engine into your application"

def copy_initializer
def copy_initializers
template "upright.rb", "config/initializers/upright.rb"
template "omniauth.rb", "config/initializers/omniauth.rb"
end

def copy_sites_config
Expand Down Expand Up @@ -43,8 +44,8 @@ def show_post_install_message
say " 1. Run migrations: bin/rails db:migrate"
say " 2. Configure your servers in config/deploy.yml"
say " 3. Configure sites in config/sites.yml"
say " 4. Add probes in config/probes/*.yml"
say " 5. Configure authentication in config/initializers/upright.rb"
say " 4. Add probes in probes/*.yml"
say " 5. Set ADMIN_PASSWORD env var (default: upright)"
say ""
say "For production, review config/initializers/upright.rb and update:"
say " config.hostname = \"honcho-upright.com\""
Expand Down
8 changes: 8 additions & 0 deletions lib/generators/upright/install/templates/omniauth.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# WARNING: Change the default password before deploying to production!
# Set the ADMIN_PASSWORD environment variable or update the credentials below.

Rails.application.config.middleware.use OmniAuth::Builder do
provider :static_credentials,
title: "Sign In",
credentials: { "admin" => ENV.fetch("ADMIN_PASSWORD", "upright") }
end
3 changes: 0 additions & 3 deletions lib/generators/upright/install/templates/upright.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,4 @@
# client_id: ENV["OIDC_CLIENT_ID"],
# client_secret: ENV["OIDC_CLIENT_SECRET"]
# }
#
# No authentication (internal networks only)
config.auth_provider = nil
end
57 changes: 57 additions & 0 deletions lib/omniauth/strategies/static_credentials.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
require "omniauth"

module OmniAuth
module Strategies
class StaticCredentials
include OmniAuth::Strategy

option :name, "static_credentials"
option :title, "Sign In"
option :credentials, {}

def request_phase
OmniAuth::Form.build(title: options.title, url: callback_path) do
text_field "Username", "username"
password_field "Password", "password"
end.to_response
end

def callback_phase
if valid_credentials?
super
else
fail!(:invalid_credentials)
end
end

uid { username }

info do
{ name: username, email: "#{username}@localhost" }
end

protected

def valid_credentials?
return false if username.blank? || password.blank?

configured_credentials.any? do |user, pass|
ActiveSupport::SecurityUtils.secure_compare(username, user.to_s) &&
ActiveSupport::SecurityUtils.secure_compare(password, pass.to_s)
end
end

def configured_credentials
options.credentials || {}
end

def username
request.params["username"]
end

def password
request.params["password"]
end
end
end
end
1 change: 1 addition & 0 deletions lib/upright.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require "omniauth"
require "omniauth_openid_connect"
require "omniauth/rails_csrf_protection"
require "omniauth/strategies/static_credentials"
require "propshaft"
require "importmap-rails"
require "turbo-rails"
Expand Down
11 changes: 3 additions & 8 deletions lib/upright/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def initialize
@playwright_server_url = ENV["PLAYWRIGHT_SERVER_URL"]
@otel_endpoint = ENV["OTEL_EXPORTER_OTLP_ENDPOINT"]

@auth_provider = nil
@auth_provider = :static_credentials
@auth_options = {}
end

Expand Down Expand Up @@ -86,25 +86,20 @@ def hostname=(value)
end

def hostname
if Rails.env.local?
@hostname&.sub(/\.[^.]+\z/, ".localhost")
else
@hostname
end
@hostname
end

def default_url_options
if Rails.env.production?
{ protocol: "https", host: "#{admin_subdomain}.#{hostname}", domain: hostname }
else
{ protocol: "http", host: "#{admin_subdomain}.#{hostname}", port: 3040, domain: hostname }
{ protocol: "http", host: "#{admin_subdomain}.#{hostname}", port: ENV.fetch("PORT", 3000).to_i, domain: hostname }
end
end

private
def configure_allowed_hosts
Rails.application.config.hosts = [ /.*\.#{Regexp.escape(hostname)}/, hostname ]
Rails.application.config.action_controller.default_url_options = default_url_options
Rails.application.config.action_dispatch.tld_length = 1
end
end
2 changes: 1 addition & 1 deletion lib/upright/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class Upright::Engine < ::Rails::Engine
# Start metrics server when Solid Queue runs standalone (not embedded in Puma)
initializer "upright.solid_queue_metrics" do
SolidQueue.on_start do
unless ENV["SOLID_QUEUE_IN_PUMA"]
unless ENV["SOLID_QUEUE_IN_PUMA"] || Rails.env.local?
ENV["PROMETHEUS_EXPORTER_PORT"] ||= "9394"
ENV["PROMETHEUS_EXPORTER_LOG_REQUESTS"] = "false"
Yabeda::Prometheus::Exporter.start_metrics_server!
Expand Down
1 change: 1 addition & 0 deletions test/dummy/.foreman
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
port: 3000
2 changes: 2 additions & 0 deletions test/dummy/Procfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
web: bin/rails server -b '0.0.0.0' -p $PORT
jobs: bin/rails solid_queue:start
10 changes: 8 additions & 2 deletions test/dummy/bin/dev
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
#!/usr/bin/env ruby
exec "./bin/rails", "server", *ARGV
#!/usr/bin/env bash

if ! gem list foreman -i --silent; then
echo "Installing foreman..."
gem install foreman
fi

exec foreman start -f Procfile.dev "$@"
6 changes: 6 additions & 0 deletions test/dummy/bin/services
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -eu
cd "$(dirname "${BASH_SOURCE[0]}")/.."

docker compose up -d "$@" --remove-orphans
docker compose ps
17 changes: 0 additions & 17 deletions test/dummy/config/initializers/0_url_options.rb

This file was deleted.

19 changes: 6 additions & 13 deletions test/dummy/config/initializers/omniauth.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
# WARNING: Change the default password before deploying to production!
# Set the ADMIN_PASSWORD environment variable or update the credentials below.

Rails.application.config.middleware.use OmniAuth::Builder do
provider :openid_connect,
name: :duo,
issuer: "https://example.auth0.com",
discovery: false,
scope: %i[ openid email profile ],
client_options: {
identifier: "test-client-id",
secret: "test-client-secret",
redirect_uri: "http://app.upright.localhost:3040/auth/duo/callback",
authorization_endpoint: "https://example.auth0.com/authorize",
token_endpoint: "https://example.auth0.com/oauth/token",
userinfo_endpoint: "https://example.auth0.com/userinfo"
}
provider :static_credentials,
title: "Upright Sign In",
credentials: { "admin" => ENV.fetch("ADMIN_PASSWORD", "upright") }
end
5 changes: 2 additions & 3 deletions test/dummy/config/initializers/upright.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
Upright.configure do |config|
config.probes_path = Rails.root.join("config/upright/probes")
config.frozen_record_path = Rails.root.join("config")
config.hostname = "upright.localhost"
config.user_agent = "Upright-Test/1.0"
config.auth_provider = :openid_connect
config.auth_provider = :static_credentials
end
38 changes: 28 additions & 10 deletions test/dummy/config/recurring.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
# examples:
# periodic_cleanup:
# class: CleanSoftDeletedRecordsJob
# queue: background
# args: [ 1000, { batch_size: 500 } ]
# schedule: every hour
# periodic_cleanup_with_command:
# command: "SoftDeletedRecord.due.delete_all"
# priority: 2
# schedule: at 5am every day
development:
http_probes:
schedule: every minute
command: "Upright::Probes::HTTPProbe.check_and_record_all_later"

smtp_probes:
schedule: every minute
command: "Upright::Probes::SMTPProbe.check_and_record_all_later"

playwright_example_probe:
schedule: every 5 minutes
command: "Probes::Playwright::ExampleProbe.check_and_record_later"

production:
http_probes:
schedule: "*/30 * * * * *"
command: "Upright::Probes::HTTPProbe.check_and_record_all_later"

smtp_probes:
schedule: "*/30 * * * * *"
command: "Upright::Probes::SMTPProbe.check_and_record_all_later"

playwright_example_probe:
schedule: every 5 minutes
command: "Probes::Playwright::ExampleProbe.check_and_record_later"

clear_solid_queue_finished_jobs:
command: "SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)"
schedule: every hour at minute 12

cleanup_stale_probe_results:
command: "Upright::ProbeResult.stale.in_batches.destroy_all"
schedule: every hour at minute 30
7 changes: 0 additions & 7 deletions test/dummy/config/upright/probes/smtp_probes.yml

This file was deleted.

Loading