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

[cmd] Implement a rootless deprecation messages #6091

Merged
merged 2 commits into from
Jul 14, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions cmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,24 @@ import (
"path"

"github.com/spf13/cobra"

"github.com/open-policy-agent/opa/cmd/internal/deprecation"
)

// RootCommand is the base CLI command that all subcommands are added to.
var RootCommand = &cobra.Command{
Use: path.Base(os.Args[0]),
Short: "Open Policy Agent (OPA)",
Long: "An open source project to policy-enable your service.",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
charlieegan3 marked this conversation as resolved.
Show resolved Hide resolved

message, fatal := deprecation.CheckWarnings(os.Environ(), cmd.Use)
if message != "" {
cmd.PrintErr(message)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooh I didn't know that cobra.Command had that 💡

if fatal {
os.Exit(1)
}
}

},
}
54 changes: 54 additions & 0 deletions cmd/internal/deprecation/rootless.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package deprecation

// TODO: these warnings can be removed when the rootless images are no longer published.

const rootlessWarningMessage = `OPA appears to be running in a deprecated -rootless image.
Since v0.50.0, the default OPA images have been configured to use a non-root
user.

This image will soon cease to be updated. The following images should now be
used instead:

* openpolicyagent/opa:latest and NOT (openpolicyagent/opa:latest-rootless)
* openpolicyagent/opa:edge and NOT (openpolicyagent/opa:edge-rootless)
* openpolicyagent/opa:X.Y.Z and NOT (openpolicyagent/opa:X.Y.Z-rootless)

You can choose to acknowledge and ignore this message by unsetting:
OPA_DOCKER_IMAGE_TAG=rootless
`

// warningRootless is a fatal warning is triggered when the user is running OPA
// in a deprecated rootless image.
var warningRootless = warning{
MatchEnv: func(env []string) bool {
for _, e := range env {
if e == "OPA_DOCKER_IMAGE_TAG=rootless" {
return true
}
}
return false
},
MatchCommand: func(name string) bool {
return name != "run"
},
Fatal: true,
Message: rootlessWarningMessage,
}

// warningRootlessRun is a non-fatal version of the warning reserved for opa run.
// The warning for run is non-fatal to avoid production disruption
var warningRootlessRun = warning{
MatchEnv: func(env []string) bool {
for _, e := range env {
if e == "OPA_DOCKER_IMAGE_TAG=rootless" {
return true
}
}
return false
},
MatchCommand: func(name string) bool {
return name == "run"
},
Fatal: false,
Message: rootlessWarningMessage,
}
92 changes: 92 additions & 0 deletions cmd/internal/deprecation/warning.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package deprecation

import (
"bytes"
"fmt"
"io"
"strings"
)

const width, border = 80, 3
const titleChar, dividerChar = "#", "-"

// warning is a struct which can be used to define deprecation warnings based on
// the environment and command being run.
type warning struct {
MatchEnv func([]string) bool
MatchCommand func(string) bool
Fatal bool
Message string
}

// CheckWarnings runs messageForWarnings with a default set of real warnings.
func CheckWarnings(env []string, command string) (string, bool) {
warnings := []warning{
warningRootless,
warningRootlessRun,
}

return messageForWarnings(warnings, env, command)
}

// messageForWarnings returns an obnoxious banner with the contents of all firing warnings.
// If no warnings fire, it returns an empty string.
// If any warnings are fatal, it returns true for the second return value.
func messageForWarnings(warnings []warning, env []string, command string) (string, bool) {
var messages []string
var fatal bool

for _, w := range warnings {
if w.MatchEnv(env) && w.MatchCommand(command) {
messages = append(messages, w.Message)
if w.Fatal {
fatal = true
}
}
}

buf := bytes.NewBuffer(nil)

if len(messages) == 0 {
return "", false
}

title := "Deprecation Warnings"
if fatal {
title = "Fatal Deprecation Warnings"
}

printFormattedTitle(buf, title)

for i, msg := range messages {
fmt.Fprintln(buf, strings.TrimSpace(msg))
if i < len(messages)-1 {
printFormattedDivider(buf)
}
}

printFormattedTitle(buf, "end "+title)

return buf.String(), fatal
}

func printFormattedTitle(out io.Writer, title string) {
padding := (width - len(title) - border*2) / 2

fmt.Fprintln(out, strings.Repeat(titleChar, width))
fmt.Fprintln(out,
strings.Join(
[]string{
strings.Repeat(titleChar, border),
strings.Repeat(" ", padding), strings.ToUpper(title), strings.Repeat(" ", padding),
strings.Repeat(titleChar, border),
},
"",
),
)
fmt.Fprintln(out, strings.Repeat(titleChar, width))
}

func printFormattedDivider(out io.Writer) {
fmt.Fprintln(out, strings.Repeat(dividerChar, width))
}
163 changes: 163 additions & 0 deletions cmd/internal/deprecation/warning_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package deprecation

import (
"testing"
)

func TestMessageForWarnings(t *testing.T) {
testCases := map[string]struct {
Env []string
Command string
Warnings []warning
ExpectedMessage string
ExpectedFatal bool
}{
"warning that does not fire": {
Env: []string{"OPA_FOOBAR=1"},
Command: "foobar",
Warnings: []warning{
{
MatchEnv: func(env []string) bool {
return false
},
MatchCommand: func(command string) bool {
return false
},
Fatal: true,
Message: "fatal warning",
},
},
ExpectedFatal: false,
ExpectedMessage: "",
},
"warning that fires": {
Env: []string{"OPA_FOOBAR=1"},
Command: "foobar",
Warnings: []warning{
{
MatchEnv: func(env []string) bool {
for _, e := range env {
if e == "OPA_FOOBAR=1" {
return true
}
}
return false
},
MatchCommand: func(command string) bool {
return command == "foobar"
},
Fatal: true,
Message: "fatal warning for foobar",
},
},
ExpectedMessage: `################################################################################
### FATAL DEPRECATION WARNINGS ###
################################################################################
fatal warning for foobar
################################################################################
### END FATAL DEPRECATION WARNINGS ###
################################################################################
`,
ExpectedFatal: true,
},
"two warnings that fire, one fatally": {
Env: []string{"OPA_FOOBAR=1"},
Command: "foobar",
Warnings: []warning{
{
MatchEnv: func(env []string) bool {
for _, e := range env {
if e == "OPA_FOOBAR=1" {
return true
}
}
return false
},
MatchCommand: func(command string) bool {
return command == "foobar"
},
Fatal: true,
Message: "fatal warning for foobar",
},
{
MatchEnv: func(env []string) bool {
return true
},
MatchCommand: func(command string) bool {
return command == "foobar"
},
Fatal: false,
Message: "non fatal warning for foobar",
},
},
ExpectedMessage: `################################################################################
### FATAL DEPRECATION WARNINGS ###
################################################################################
fatal warning for foobar
--------------------------------------------------------------------------------
non fatal warning for foobar
################################################################################
### END FATAL DEPRECATION WARNINGS ###
################################################################################
`,
ExpectedFatal: true,
},
"two warnings that fire, neither fatally": {
Env: []string{"OPA_FOOBAR=1"},
Command: "foobar",
Warnings: []warning{
{
MatchEnv: func(env []string) bool {
for _, e := range env {
if e == "OPA_FOOBAR=1" {
return true
}
}
return false
},
MatchCommand: func(command string) bool {
return command == "foobar"
},
Fatal: false,
Message: "warning for foobar",
},
{
MatchEnv: func(env []string) bool {
return true
},
MatchCommand: func(command string) bool {
return command == "foobar"
},
Fatal: false,
Message: "another warning for foobar",
},
},
ExpectedMessage: `################################################################################
### DEPRECATION WARNINGS ###
################################################################################
warning for foobar
--------------------------------------------------------------------------------
another warning for foobar
################################################################################
### END DEPRECATION WARNINGS ###
################################################################################
`,
ExpectedFatal: false,
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
message, fatal := messageForWarnings(tc.Warnings, tc.Env, tc.Command)

if fatal != tc.ExpectedFatal {
t.Errorf("Expected fatal to be %v but got %v", tc.ExpectedFatal, fatal)
}

if message != tc.ExpectedMessage {
t.Errorf("Expected message\n%s\nbut got\n%s", tc.ExpectedMessage, message)
}

})
}
}
6 changes: 0 additions & 6 deletions runtime/check_user_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package runtime

import (
"os"
"os/user"

"github.com/open-policy-agent/opa/logging"
Expand All @@ -21,9 +20,4 @@ func checkUserPrivileges(logger logging.Logger) {
message := "OPA running with uid or gid 0. Running OPA with root privileges is not recommended."
logger.Warn(message)
}

if os.Getenv("OPA_DOCKER_IMAGE_TAG") == "rootless" {
message := "The -rootless image tag will not be published after OPA v0.52.0."
logger.Error(message)
}
}