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

PMM-11714 Don't return an error if API key can't be created. #2469

Merged
merged 17 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from 13 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ compose.yml

cli-tests/node_modules/
cli-tests/playwright-report/

api-tests/pmm-api-tests-output.txt
api-tests/pmm-api-tests-junit-report.xml
5 changes: 5 additions & 0 deletions admin/commands/management/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@ var registerResultT = commands.ParseTemplate(`
pmm-agent registered.
pmm-agent ID: {{ .PMMAgent.AgentID }}
Node ID : {{ .PMMAgent.RunsOnNodeID }}
{{ if .Warning }}
Warning: {{ .Warning }}
{{- end -}}
`)

type registerResult struct {
GenericNode *node.RegisterNodeOKBodyGenericNode `json:"generic_node"`
ContainerNode *node.RegisterNodeOKBodyContainerNode `json:"container_node"`
PMMAgent *node.RegisterNodeOKBodyPMMAgent `json:"pmm_agent"`
Warning string `json:"warning"`
}

func (res *registerResult) Result() {}
Expand Down Expand Up @@ -96,5 +100,6 @@ func (cmd *RegisterCommand) RunCmd() (commands.Result, error) {
GenericNode: resp.Payload.GenericNode,
ContainerNode: resp.Payload.ContainerNode,
PMMAgent: resp.Payload.PMMAgent,
Warning: resp.Payload.Warning,
}, nil
}
67 changes: 67 additions & 0 deletions admin/commands/management/register_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (C) 2023 Percona LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package management

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/percona/pmm/api/managementpb/json/client/node"
)

func TestRegisterResult(t *testing.T) {
tests := []struct {
name string
result registerResult
want string
}{
{
name: "Success",
result: registerResult{
PMMAgent: &node.RegisterNodeOKBodyPMMAgent{
AgentID: "/agent_id/new_id",
RunsOnNodeID: "/node_id/second_id",
},
Warning: "",
},
want: `pmm-agent registered.
pmm-agent ID: /agent_id/new_id
Node ID : /node_id/second_id
`,
},
{
name: "With warning",
result: registerResult{
PMMAgent: &node.RegisterNodeOKBodyPMMAgent{
AgentID: "/agent_id/warning",
RunsOnNodeID: "/node_id/warning_node",
},
Warning: "Couldn't create Admin API Key",
},
want: `pmm-agent registered.
pmm-agent ID: /agent_id/warning
Node ID : /node_id/warning_node

Warning: Couldn't create Admin API Key
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, tt.result.String(), "String()")
})
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions api/managementpb/json/managementpb.json
Original file line number Diff line number Diff line change
Expand Up @@ -3889,6 +3889,11 @@
"description": "Token represents token for vmagent auth config.",
"type": "string",
"x-order": 3
},
"warning": {
"description": "Warning message.",
"type": "string",
"x-order": 4
}
}
}
Expand Down
57 changes: 34 additions & 23 deletions api/managementpb/node.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions api/managementpb/node.pb.validate.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions api/managementpb/node.proto
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ message RegisterNodeResponse {
inventory.PMMAgent pmm_agent = 3;
// Token represents token for vmagent auth config.
string token = 4;
// Warning message.
string warning = 5;
}

// Node service provides public Management API methods for Nodes.
Expand Down
5 changes: 5 additions & 0 deletions api/swagger/swagger-dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -27422,6 +27422,11 @@
"description": "Token represents token for vmagent auth config.",
"type": "string",
"x-order": 3
},
"warning": {
"description": "Warning message.",
"type": "string",
"x-order": 4
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions api/swagger/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -18591,6 +18591,11 @@
"description": "Token represents token for vmagent auth config.",
"type": "string",
"x-order": 3
},
"warning": {
"description": "Warning message.",
"type": "string",
"x-order": 4
}
}
}
Expand Down
23 changes: 14 additions & 9 deletions managed/services/grafana/auth_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,15 +533,7 @@ func cleanPath(p string) (string, error) {

func (s *AuthServer) getAuthUser(ctx context.Context, req *http.Request, l *logrus.Entry) (*authUser, *authError) {
// check Grafana with some headers from request
authHeaders := make(http.Header)
for _, k := range []string{
"Authorization",
"Cookie",
} {
if v := req.Header.Get(k); v != "" {
authHeaders.Set(k, v)
}
}
authHeaders := s.authHeaders(req)
j, err := json.Marshal(authHeaders)
if err != nil {
l.Warnf("%s", err)
Expand All @@ -558,6 +550,19 @@ func (s *AuthServer) getAuthUser(ctx context.Context, req *http.Request, l *logr
return s.retrieveRole(ctx, hash, authHeaders, l)
}

func (s *AuthServer) authHeaders(req *http.Request) http.Header {
authHeaders := make(http.Header)
for _, k := range []string{
"Authorization",
"Cookie",
} {
if v := req.Header.Get(k); v != "" {
authHeaders.Set(k, v)
}
}
return authHeaders
}

func (s *AuthServer) retrieveRole(ctx context.Context, hash string, authHeaders http.Header, l *logrus.Entry) (*authUser, *authError) {
authUser, err := s.c.getAuthUser(ctx, authHeaders)
if err != nil {
Expand Down
6 changes: 4 additions & 2 deletions managed/services/grafana/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func (c *Client) GetUserID(ctx context.Context) (int, error) {
// Ctx is used only for cancelation.
func (c *Client) getAuthUser(ctx context.Context, authHeaders http.Header) (authUser, error) {
// Check if it's API Key
if c.isAPIKeyAuth(authHeaders.Get("Authorization")) {
if c.IsAPIKeyAuth(authHeaders) {
role, err := c.getRoleForAPIKey(ctx, authHeaders)
return authUser{
role: role,
Expand Down Expand Up @@ -277,7 +277,9 @@ func (c *Client) getAuthUser(ctx context.Context, authHeaders http.Header) (auth
}, nil
}

func (c *Client) isAPIKeyAuth(authHeader string) bool {
// IsAPIKeyAuth checks if the request is made using an API Key.
func (c *Client) IsAPIKeyAuth(headers http.Header) bool {
authHeader := headers.Get("Authorization")
switch {
case strings.HasPrefix(authHeader, "Bearer"):
return true
Expand Down
15 changes: 15 additions & 0 deletions managed/services/management/mock_api_key_provider_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 26 additions & 4 deletions managed/services/management/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ import (
"context"
"fmt"
"math/rand"
"net/http"

"github.com/AlekSi/pointer"
"github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"gopkg.in/reform.v1"

Expand All @@ -35,6 +38,7 @@ import (

type apiKeyProvider interface {
CreateAdminAPIKey(ctx context.Context, name string) (int64, string, error)
IsAPIKeyAuth(headers http.Header) bool
}

// NodeService represents service for working with nodes.
Expand Down Expand Up @@ -136,10 +140,28 @@ func (s *NodeService) Register(ctx context.Context, req *managementpb.RegisterNo
return nil, e
}

apiKeyName := fmt.Sprintf("pmm-agent-%s-%d", req.NodeName, rand.Int63()) //nolint:gosec
_, res.Token, e = s.akp.CreateAdminAPIKey(ctx, apiKeyName)
if e != nil {
return nil, e
// get authorization from headers.
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
msg := "Couldn't create Admin API Key: cannot get headers from metadata"
Copy link
Contributor

Choose a reason for hiding this comment

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

was curious - is there anything fatal that can happen if we can't generate the admin role api key? and is there anything the user can do in such a case?

Copy link
Member Author

Choose a reason for hiding this comment

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

we discussed with GAS team and made decission that register shouldn't fail in case it's not possible to create an API key. So pmm-agent will keep using user credentials instead of API key.

logrus.Errorln(msg)
res.Warning = msg
return res, nil
}
authorizationHeaders := md.Get("Authorization")
if len(authorizationHeaders) == 0 {
return nil, status.Error(codes.Unauthenticated, "Authorization error.")
}
headers := make(http.Header)
headers.Set("Authorization", authorizationHeaders[0])
if !s.akp.IsAPIKeyAuth(headers) {
apiKeyName := fmt.Sprintf("pmm-agent-%s-%d", req.NodeName, rand.Int63()) //nolint:gosec
_, res.Token, e = s.akp.CreateAdminAPIKey(ctx, apiKeyName)
if e != nil {
msg := fmt.Sprintf("Couldn't create Admin API Key: %s", e)
logrus.Errorln(msg)
BupycHuk marked this conversation as resolved.
Show resolved Hide resolved
res.Warning = msg
}
}

return res, nil
Expand Down
Loading
Loading