• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mruoss / kompost / d5e64ea11ff81eea73c04c0624c0fae85c29ad8f-PR-31

pending completion
d5e64ea11ff81eea73c04c0624c0fae85c29ad8f-PR-31

Pull #31

github

mruoss
fix privilege check if no access to pg_authid
Pull Request #31: fix privilege check if no access to pg_authid

13 of 13 new or added lines in 2 files covered. (100.0%)

84 of 539 relevant lines covered (15.58%)

1.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/lib/kompost/kompo/postgres/controller/instance_controller.ex
1
defmodule Kompost.Kompo.Postgres.Controller.InstanceController do
2
  use Bonny.ControllerV2
3

4
  require Logger
5

6
  alias Kompost.Kompo.Postgres.Instance
7

8
  alias Kompost.Tools.NamespaceAccess
9

10
  step Bonny.Pluggable.SkipObservedGenerations
11

12
  step Bonny.Pluggable.Finalizer,
13
    id: "kompost.chuge.li/databases",
14
    impl: &__MODULE__.check_for_depending_databases/1,
15
    add_to_resource: true,
16
    log_level: :debug
17

18
  step Kompost.Pluggable.InitConditions, conditions: ["Credentials", "Connected", "Privileged"]
19
  step :handle_event
20

21
  @impl true
22
  def rbac_rules() do
×
23
    [to_rbac_rule({"", "secrets", ["get", "list"]})]
24
  end
25

26
  @spec handle_event(Bonny.Axn.t(), Keyword.t()) :: Bonny.Axn.t()
27
  def handle_event(%Bonny.Axn{action: action} = axn, _opts)
28
      when action in [:add, :modify, :reconcile] do
29
    id = instance_id(axn.resource)
×
30
    allowed_ns = allowed_namespaces(axn.resource)
×
31

32
    with {:cred, {:ok, connection_args}} <- {:cred, get_connection_args(axn.resource, axn.conn)},
×
33
         axn <- set_condition(axn, "Credentials", true),
×
34
         {:conn, axn, {:ok, conn}} <-
×
35
           {:conn, axn, Instance.connect(id, connection_args, allowed_ns)},
36
         axn <- set_condition(axn, "Connected", true, "Connection to database was established"),
×
37
         {:privileges, axn, :ok} <- {:privileges, axn, Instance.check_privileges(conn)},
×
38
         axn <-
×
39
           set_condition(axn, "Privileged", true, "The conneted user has the required privileges") do
40
      success_event(axn)
×
41
    else
42
      {:cred, {:error, error}} ->
43
        Logger.warning("Error when trying to fetch password.", error: error)
×
44

45
        axn
46
        |> failure_event(message: Exception.message(error))
47
        |> set_condition("Credentials", false, Exception.message(error))
×
48

49
      {:conn, axn, {:error, error}} ->
50
        message = if is_exception(error), do: Exception.message(error), else: inspect(error)
×
51
        Logger.warning("#{axn.action} failed. #{message}")
×
52

53
        axn
54
        |> failure_event(message: message)
55
        |> set_condition("Connected", false, message)
×
56

57
      {:privileges, axn, {:error, message}} ->
58
        Logger.warning("#{axn.action} failed. #{message}")
×
59

60
        axn
61
        |> failure_event(message: message)
62
        |> set_condition("Privileged", false, message)
×
63
    end
64
  end
65

66
  def handle_event(%Bonny.Axn{action: :delete} = axn, _opts) do
67
    :ok =
×
68
      axn.resource
×
69
      |> instance_id()
70
      |> Instance.disconnect()
71

72
    success_event(axn)
×
73
  end
74

75
  @spec get_connection_args(map(), K8s.Conn.t()) ::
76
          {:ok, [Postgrex.start_option()]} | {:error, K8s.Client.Runner.Base.error_t()}
77
  defp get_connection_args(%{"spec" => %{"plainPassword" => _} = spec}, _conn) do
78
    cacerts =
×
79
      case spec["ssl"]["ca"] do
80
        nil ->
81
          :public_key.cacerts_get()
×
82

83
        cacert ->
84
          cacert
85
          |> :public_key.pem_decode()
86
          |> Enum.map(fn {_, der, _} -> der end)
×
87
      end
88

89
    {:ok,
90
     [
91
       hostname: spec["hostname"],
92
       port: spec["port"],
93
       username: spec["username"],
94
       password: spec["plainPassword"],
95
       database: "postgres",
96
       ssl: spec["ssl"]["enabled"] || false,
×
97
       ssl_opts: [
98
         verify: (spec["ssl"]["verify"] || "verify_none") |> String.to_atom(),
×
99
         cacerts: cacerts,
100
         server_name_indication: String.to_charlist(spec["hostname"])
101
       ]
102
     ]}
103
  end
104

105
  defp get_connection_args(instance, conn) do
106
    %{
107
      "spec" => %{"passwordSecretRef" => %{"name" => secret_name, "key" => key}}
108
    } = instance
×
109

110
    with {:ok, secret} <-
×
111
           K8s.Client.get("v1", "Secret",
112
             name: secret_name,
113
             namespace:
114
               instance["metadata"]["namespace"] ||
×
115
                 Application.fetch_env!(:kompost, :operator_namespace)
×
116
           )
117
           |> K8s.Client.put_conn(conn)
118
           |> K8s.Client.run() do
119
      password = Base.decode64!(secret["data"][key])
×
120
      instance = put_in(instance, ~w(spec plainPassword), password)
×
121
      get_connection_args(instance, conn)
×
122
    end
123
  end
124

125
  @doc """
126
  A finalizer preventing the deletion of an instance if there are database
127
  resources in the cluster which still depend on it.
128
  """
129
  @spec check_for_depending_databases(Bonny.Axn.t()) ::
130
          {:ok, Bonny.Axn.t()} | {:error, Bonny.Axn.t()}
131
  def check_for_depending_databases(%Bonny.Axn{resource: resource, conn: conn} = axn) do
132
    {:ok, result} =
×
133
      K8s.Client.list("kompost.chuge.li/v1alpha1", "PostgresDatabase", namespace: :all)
134
      |> K8s.Client.put_conn(conn)
135
      |> K8s.Client.run()
136

137
    if Enum.any?(
×
138
         result["items"],
139
         &(is_nil(&1["metadata"]["deletionTimestamp"]) &&
×
140
             &1["spec"]["instanceRef"]["name"] == resource["metadata"]["name"] and
×
141
             &1["spec"]["instanceRef"]["namespace"] == resource["metadata"]["namespace"])
×
142
       ) do
143
      {:error, axn}
144
    else
145
      {:ok, axn}
146
    end
147
  end
148

149
  @spec instance_id(resource :: map()) :: Instance.id()
150
  defp instance_id(%{"kind" => "PostgresInstance", "metadata" => metadata}),
×
151
    do: {metadata["namespace"], metadata["name"]}
152

153
  defp instance_id(%{"kind" => "PostgresClusterInstance", "metadata" => metadata}),
×
154
    do: {:cluster, metadata["name"]}
155

156
  @spec allowed_namespaces(map()) :: NamespaceAccess.allowed_namespaces()
157
  defp(allowed_namespaces(%{"kind" => "PostgresInstance"} = resource)) do
×
158
    [~r/^#{resource["metadata"]["namespace"]}$/]
×
159
  end
160

161
  defp allowed_namespaces(%{"kind" => "PostgresClusterInstance"} = resource) do
162
    NamespaceAccess.allowed_namespaces!(resource)
×
163
  end
164
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc