Skip to content

Commit fb44fdd

Browse files
Allow the GARM agent to connect to GARM insecurely
This is mostly for testing and as a temporary stopgap for environments that need time to transition GARM to TLS. The change surfaces the force_insecure option added in the garm-agent v0.1.1. Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
1 parent 3344a17 commit fb44fdd

19 files changed

Lines changed: 285 additions & 3 deletions

cmd/garm-cli/cmd/controller.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ available upstream can be listed with "garm-cli controller tools list --online".
163163
if cmd.Flags().Changed("enable-tools-sync") {
164164
params.SyncGARMAgentTools = &enableToolsSync
165165
}
166+
if cmd.Flags().Changed("allow-insecure-agent") {
167+
params.AllowInsecureGARMAgent = &allowInsecureAgent
168+
}
166169
if cmd.Flags().Changed("clear-ca-bundle") {
167170
params.ClearCACertBundle = &clearCABundle
168171
}
@@ -642,6 +645,7 @@ func renderControllerInfoTable(info params.ControllerInfo) string {
642645
agentVersion = params.GARMAgentLatestVersion
643646
}
644647
t.AppendRow(table.Row{"GARM agent version", agentVersion})
648+
t.AppendRow(table.Row{"Allow insecure agent", info.AllowInsecureGARMAgent})
645649
t.AppendRow(table.Row{"Minimum Job Age Backoff", info.MinimumJobAgeBackoff})
646650
t.AppendRow(table.Row{"Version", serverVersion})
647651
if len(info.CACertBundle) > 0 {
@@ -667,6 +671,7 @@ func init() {
667671
controllerUpdateCmd.Flags().StringVarP(&garmToolsReleasesURL, "garm-tools-url", "t", "", "The URL for the garm-agent releases page (ie. https://api.github.com/repos/cloudbase/garm-agent/releases)")
668672
controllerUpdateCmd.Flags().StringVar(&garmAgentVersion, "garm-agent-version", "", "Pin the GARM agent version to use (a semver version like v0.1.0). Use \"latest\" to track the newest release.")
669673
controllerUpdateCmd.Flags().BoolVarP(&enableToolsSync, "enable-tools-sync", "s", false, "Enable or disable automatic garm tools sync.")
674+
controllerUpdateCmd.Flags().BoolVar(&allowInsecureAgent, "allow-insecure-agent", false, "Configure deployed garm-agents to connect to GARM over plain http/ws (force_insecure). The agent token is sent in plain text; meant for local development and testing only.")
670675
controllerUpdateCmd.Flags().UintVarP(&minimumJobAgeBackoff, "minimum-job-age-backoff", "b", 0, "The minimum job age backoff for the controller")
671676
controllerUpdateCmd.Flags().StringVar(&controllerCABundle, "ca-bundle", "", "A CA bundle that will be used by GARM and the runners to validate HTTPS connections.")
672677
controllerUpdateCmd.Flags().BoolVar(&clearCABundle, "clear-ca-bundle", false, "Remove the currently configured CA bundle from the controller.")

cmd/garm-cli/cmd/init.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ var (
3939
garmToolsReleasesURL string
4040
garmAgentVersion string
4141
enableToolsSync bool
42+
allowInsecureAgent bool
4243
minimumJobAgeBackoff uint
4344
controllerCABundle string
4445
clearCABundle bool

database/sql/controller.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ func dbControllerToCommonController(dbInfo ControllerInfo) (params.ControllerInf
7878
GARMAgentReleasesURL: dbInfo.GARMAgentReleasesURL,
7979
SyncGARMAgentTools: dbInfo.SyncGARMAgentTools,
8080
GARMAgentVersion: dbInfo.GARMAgentVersion,
81+
AllowInsecureGARMAgent: dbInfo.AllowInsecureGARMAgent,
8182
CachedGARMAgentReleaseFetchedAt: dbInfo.CachedGARMAgentReleaseFetchedAt,
8283
CachedGARMAgentReleases: dbInfo.CachedGARMAgentReleases,
8384
CACertBundle: dbInfo.CACertBundle,
@@ -217,6 +218,10 @@ func controllerUpdates(info params.UpdateControllerParams, dbInfo ControllerInfo
217218
}
218219
}
219220

221+
if info.AllowInsecureGARMAgent != nil && *info.AllowInsecureGARMAgent != dbInfo.AllowInsecureGARMAgent {
222+
updates["allow_insecure_garm_agent"] = *info.AllowInsecureGARMAgent
223+
}
224+
220225
if info.MinimumJobAgeBackoff != nil && *info.MinimumJobAgeBackoff != dbInfo.MinimumJobAgeBackoff {
221226
updates["minimum_job_age_backoff"] = *info.MinimumJobAgeBackoff
222227
}

database/sql/controller_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,36 @@ func (s *CtrlTestSuite) TestUpdateControllerGARMAgentVersion() {
113113
s.Require().Equal("v2.0.0", info.GARMAgentVersion)
114114
}
115115

116+
func (s *CtrlTestSuite) TestUpdateControllerAllowInsecureGARMAgent() {
117+
_, err := s.Store.InitController()
118+
s.Require().NoError(err)
119+
120+
// The flag defaults to off.
121+
info, err := s.Store.ControllerInfo()
122+
s.Require().NoError(err)
123+
s.Require().False(info.AllowInsecureGARMAgent)
124+
125+
// Enabling it persists and round-trips through ControllerInfo.
126+
allow := true
127+
info, err = s.Store.UpdateController(params.UpdateControllerParams{AllowInsecureGARMAgent: &allow})
128+
s.Require().NoError(err)
129+
s.Require().True(info.AllowInsecureGARMAgent)
130+
info, err = s.Store.ControllerInfo()
131+
s.Require().NoError(err)
132+
s.Require().True(info.AllowInsecureGARMAgent)
133+
134+
// A nil pointer leaves the stored value untouched.
135+
info, err = s.Store.UpdateController(params.UpdateControllerParams{})
136+
s.Require().NoError(err)
137+
s.Require().True(info.AllowInsecureGARMAgent)
138+
139+
// It can be turned off again.
140+
allow = false
141+
info, err = s.Store.UpdateController(params.UpdateControllerParams{AllowInsecureGARMAgent: &allow})
142+
s.Require().NoError(err)
143+
s.Require().False(info.AllowInsecureGARMAgent)
144+
}
145+
116146
func (s *CtrlTestSuite) TestControllerInfoResolvesCachedTools() {
117147
_, err := s.Store.InitController()
118148
s.Require().NoError(err)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2026 Cloudbase Solutions SRL
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
// not use this file except in compliance with the License. You may obtain
5+
// a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
// License for the specific language governing permissions and limitations
13+
// under the License.
14+
15+
package migrations
16+
17+
import (
18+
"github.com/go-gormigrate/gormigrate/v2"
19+
"gorm.io/gorm"
20+
)
21+
22+
// controllerInfo0005 is a minimal stub that only declares the new column:
23+
// whether deployed garm-agents are configured to connect to GARM over
24+
// plain http/ws (the agent's force_insecure setting).
25+
26+
type controllerInfo0005 struct {
27+
AllowInsecureGARMAgent bool
28+
}
29+
30+
func (controllerInfo0005) TableName() string { return "controller_infos" }
31+
32+
func init() {
33+
Register(&gormigrate.Migration{
34+
ID: "0005_allow_insecure_garm_agent",
35+
Migrate: func(tx *gorm.DB) error {
36+
return tx.AutoMigrate(&controllerInfo0005{})
37+
},
38+
})
39+
}

database/sql/models.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ type ControllerInfo struct {
8181
CachedGARMAgentReleases datatypes.JSON
8282
// CachedGARMAgentReleaseFetchedAt is the timestamp when the release index was last fetched
8383
CachedGARMAgentReleaseFetchedAt *time.Time
84+
// AllowInsecureGARMAgent tells GARM to configure the garm-agent (when used) to connect
85+
// to the GARM API even when not using TLS.
86+
AllowInsecureGARMAgent bool
8487

8588
// CACertBundle holds a certificate bundle meant to validate the certificate
8689
// used by GARM itself. This can be just the root certificate that can validate

internal/templates/templates.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ type InstallContext struct {
4040
AgentURL string
4141
AgentToken string
4242
AgentShell string // "true" or "false", used verbatim in TOML config
43+
// ForceInsecureGARMAgent allows the agent to connect back to GARM even when
44+
// TLS is not enabled in GARM itself.
45+
ForceInsecureGARMAgent bool
4346
}
4447

4548
func GetTemplateContent(osType commonParams.OSType, forge params.EndpointType) ([]byte, error) {
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2026 Cloudbase Solutions SRL
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
// not use this file except in compliance with the License. You may obtain
5+
// a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
// License for the specific language governing permissions and limitations
13+
// under the License.
14+
15+
package templates
16+
17+
import (
18+
"fmt"
19+
"strings"
20+
"testing"
21+
22+
commonParams "github.com/cloudbase/garm-provider-common/params"
23+
"github.com/cloudbase/garm/params"
24+
)
25+
26+
// TestInstallTemplatesRenderForceInsecure renders every default install
27+
// template and checks that the agent config only carries force_insecure when
28+
// the controller allows insecure agent connections. Agents older than v0.1.1
29+
// do not know the setting, so it must be absent unless explicitly enabled.
30+
func TestInstallTemplatesRenderForceInsecure(t *testing.T) {
31+
for _, forge := range []params.EndpointType{params.GithubEndpointType, params.GiteaEndpointType} {
32+
for _, osType := range []commonParams.OSType{commonParams.Linux, commonParams.Windows} {
33+
t.Run(fmt.Sprintf("%s_%s", forge, osType), func(t *testing.T) {
34+
content, err := GetTemplateContent(osType, forge)
35+
if err != nil {
36+
t.Fatalf("failed to get template content: %v", err)
37+
}
38+
39+
tplCtx := InstallContext{
40+
AgentMode: true,
41+
AgentURL: "wss://garm.example.com/agent",
42+
AgentToken: "secret",
43+
AgentShell: "false",
44+
}
45+
46+
rendered, err := RenderRunnerInstallScript(string(content), tplCtx)
47+
if err != nil {
48+
t.Fatalf("failed to render template: %v", err)
49+
}
50+
if strings.Contains(string(rendered), "force_insecure") {
51+
t.Error("expected force_insecure to be absent when not allowed")
52+
}
53+
54+
tplCtx.ForceInsecureGARMAgent = true
55+
rendered, err = RenderRunnerInstallScript(string(content), tplCtx)
56+
if err != nil {
57+
t.Fatalf("failed to render template: %v", err)
58+
}
59+
if !strings.Contains(string(rendered), "force_insecure = true") {
60+
t.Error("expected force_insecure = true in the agent config")
61+
}
62+
// The neighboring config keys must survive the conditional.
63+
if !strings.Contains(string(rendered), "enable_shell = ") {
64+
t.Error("expected enable_shell to remain in the agent config")
65+
}
66+
})
67+
}
68+
}
69+
}

internal/templates/userdata/gitea_linux_userdata.tmpl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ server_url = "$AGENT_URL"
8686
log_file = "/var/log/garm-agent/garm-agent.log"
8787
work_dir = "$RUN_HOME"
8888
enable_shell = $AGENT_SHELL
89+
{{- if .ForceInsecureGARMAgent }}
90+
force_insecure = true
91+
{{- end }}
8992
token = "$AGENT_TOKEN"
9093
runner_cmdline = ["$RUN_HOME/gitea-runner", "daemon", "--once"]
9194
state_db_path = "/etc/garm-agent/agent-state.db"

internal/templates/userdata/gitea_windows_userdata.tmpl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,9 @@ server_url = "$agentURL"
430430
log_file = "C:/garm-agent/garm-agent.log"
431431
shell = "cmd.exe"
432432
enable_shell = $shellEnabled
433+
{{- if .ForceInsecureGARMAgent }}
434+
force_insecure = true
435+
{{- end }}
433436
work_dir = "C:/actions-runner/"
434437
token = "$agentToken"
435438
runner_cmdline = ["$runnerExecutable", "daemon", "--once"]

0 commit comments

Comments
 (0)