github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/server/controller_admin_test.go (about)

     1  package server
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"net/http"
     8  	"net/url"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/pyroscope-io/pyroscope/pkg/model"
    13  	"github.com/pyroscope-io/pyroscope/pkg/service"
    14  	"github.com/pyroscope-io/pyroscope/pkg/sqlstore"
    15  
    16  	. "github.com/onsi/ginkgo/v2"
    17  	. "github.com/onsi/gomega"
    18  	"github.com/prometheus/client_golang/prometheus"
    19  	"github.com/sirupsen/logrus"
    20  
    21  	"github.com/pyroscope-io/pyroscope/pkg/config"
    22  	"github.com/pyroscope-io/pyroscope/pkg/exporter"
    23  	"github.com/pyroscope-io/pyroscope/pkg/health"
    24  	"github.com/pyroscope-io/pyroscope/pkg/parser"
    25  	"github.com/pyroscope-io/pyroscope/pkg/storage"
    26  	"github.com/pyroscope-io/pyroscope/pkg/testing"
    27  )
    28  
    29  var _ = Describe("server", func() {
    30  	type testServices struct {
    31  		s *storage.Storage
    32  		k service.APIKeyService
    33  	}
    34  	ingest := func(lines string, app string) {
    35  		u, _ := url.Parse("http://localhost:4040/ingest")
    36  		q := u.Query()
    37  		q.Add("name", app)
    38  		q.Add("from", strconv.Itoa(int(testing.ParseTime("2020-01-01-01:01:00").Unix())))
    39  		q.Add("until", strconv.Itoa(int(testing.ParseTime("2020-01-01-01:01:10").Unix())))
    40  		q.Add("format", "lines")
    41  		u.RawQuery = q.Encode()
    42  		req, err := http.NewRequest("POST", u.String(), bytes.NewBuffer([]byte(lines)))
    43  		Expect(err).ToNot(HaveOccurred())
    44  		req.Header.Set("Content-Type", "text/plain")
    45  		res, err := http.DefaultClient.Do(req)
    46  		Expect(err).ToNot(HaveOccurred())
    47  		Expect(res.StatusCode).To(Equal(200))
    48  	}
    49  	deleteApp := func(app string, key string) *http.Response {
    50  		input := struct {
    51  			Name string `json:"name"`
    52  		}{app}
    53  		body, _ := json.Marshal(input)
    54  		req, err := http.NewRequest("DELETE", "http://localhost:4040/api/apps", bytes.NewBuffer(body))
    55  		if key != "" {
    56  			req.Header.Set("Authorization", "Bearer "+key)
    57  		}
    58  		Expect(err).ToNot(HaveOccurred())
    59  		res, err := http.DefaultClient.Do(req)
    60  		Expect(err).ToNot(HaveOccurred())
    61  		return res
    62  	}
    63  	runServer := func(cfg **config.Config, cb func(s testServices)) {
    64  		defer GinkgoRecover()
    65  		s, err := storage.New(
    66  			storage.NewConfig(&(*cfg).Server),
    67  			logrus.StandardLogger(),
    68  			prometheus.NewRegistry(),
    69  			new(health.Controller),
    70  			storage.NoopApplicationMetadataService{},
    71  		)
    72  		Expect(err).ToNot(HaveOccurred())
    73  		e, _ := exporter.NewExporter(nil, nil)
    74  		sql, err := sqlstore.Open(&(*cfg).Server)
    75  		Expect(err).ToNot(HaveOccurred())
    76  
    77  		l := logrus.New()
    78  
    79  		c, _ := New(Config{
    80  			Configuration:           &(*cfg).Server,
    81  			Storage:                 s,
    82  			Ingester:                parser.New(logrus.StandardLogger(), s, e),
    83  			Logger:                  l,
    84  			MetricsRegisterer:       prometheus.NewRegistry(),
    85  			ExportedMetricsRegistry: prometheus.NewRegistry(),
    86  			Notifier:                mockNotifier{},
    87  			DB:                      sql.DB(),
    88  		})
    89  		c.dir = http.Dir("testdata")
    90  		startController(c, "http", ":4040")
    91  		defer c.Stop()
    92  
    93  		k := service.NewAPIKeyService(sql.DB(), 0)
    94  		cb(testServices{s, k})
    95  	}
    96  	createToken := func(s testServices, role model.Role) string {
    97  		params := model.CreateAPIKeyParams{Name: "t" + strconv.Itoa(int(role)), Role: role}
    98  		_, key, err := s.k.CreateAPIKey(context.TODO(), params)
    99  		Expect(err).ToNot(HaveOccurred())
   100  		return key
   101  	}
   102  	checkRoleAccess := func(s testServices, role model.Role, expectAccess bool, expectStatus int) {
   103  		ingest("foo;bar\nfoo;bar\nfoo;baz\nfoo;baz\nfoo;baz\n", "test.app1")
   104  		ingest("foo;bar\nfoo;bar\nfoo;baz\nfoo;baz\nfoo;baz\n", "test.app2")
   105  		time.Sleep(100 * time.Millisecond)
   106  		Expect(s.s.GetAppNames(context.TODO())).To(Equal([]string{"test.app1", "test.app2"}))
   107  		key := ""
   108  		if role != model.InvalidRole {
   109  			key = createToken(s, role)
   110  		}
   111  		res := deleteApp("test.app2", key)
   112  		Expect(res.StatusCode).To(Equal(expectStatus))
   113  		if expectAccess {
   114  			Expect(s.s.GetAppNames(context.TODO())).To(Equal([]string{"test.app1"}))
   115  		} else {
   116  			Expect(s.s.GetAppNames(context.TODO())).To(Equal([]string{"test.app1", "test.app2"}))
   117  		}
   118  	}
   119  
   120  	testing.WithConfig(func(cfg **config.Config) {
   121  		Describe("http api admin controller", func() {
   122  			Context("with Auth enabled", func() {
   123  				It("should be accessible to only admin token", func() {
   124  					(*cfg).Server.Auth.Internal.Enabled = true
   125  					(*cfg).Server.EnableExperimentalAdmin = true
   126  					runServer(cfg, func(s testServices) {
   127  						checkRoleAccess(s, model.AdminRole, true, http.StatusOK)
   128  						checkRoleAccess(s, model.AgentRole, false, http.StatusForbidden)
   129  						checkRoleAccess(s, model.ReadOnlyRole, false, http.StatusForbidden)
   130  						checkRoleAccess(s, model.InvalidRole, false, http.StatusUnauthorized)
   131  					})
   132  				})
   133  			})
   134  			Context("with Auth disabled", func() {
   135  				// notice that the routes' access is granular
   136  				// FIXME update the tests to test each route individually
   137  				It("should never be accessible", func() {
   138  					(*cfg).Server.Auth.Internal.Enabled = false
   139  					(*cfg).Server.EnableExperimentalAdmin = true
   140  					runServer(cfg, func(s testServices) {
   141  						checkRoleAccess(s, model.AdminRole, false, http.StatusForbidden)
   142  						checkRoleAccess(s, model.AgentRole, false, http.StatusForbidden)
   143  						checkRoleAccess(s, model.ReadOnlyRole, false, http.StatusForbidden)
   144  						checkRoleAccess(s, model.InvalidRole, false, http.StatusForbidden)
   145  					})
   146  				})
   147  			})
   148  		})
   149  	})
   150  })