add a dedicated endpoint that redirects to fleet_desktop.transparency_url (#6204)

As part of https://github.com/fleetdm/fleet/issues/5947, and in order to have a simplified workflow in Fleet Desktop, we defined https://github.com/fleetdm/fleet/issues/6200 to add a new endpoint that redirects to the transparency url as defined in the config (for premium users only)

```
~/projects/fleet $ curl -v -s https://localhost:8080/api/latest/fleet/device/bf34ab98-23b0-48bc-8e82-8c0143cba11c/transparency
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 307
< content-type: application/json; charset=utf-8
< location: https://fleetdm.com/transparency
< content-length: 0
< date: Mon, 13 Jun 2022 18:09:29 GMT
<
* Connection #0 to host localhost left intact
```
This commit is contained in:
Roberto Dip 2022-06-13 16:07:08 -03:00 committed by GitHub
parent ef6ae42d86
commit 19c5e3545b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 71 additions and 33 deletions

View file

@ -1,4 +1,3 @@
- Add `fleet_desktop.transparency_url` to `app_config_json`
- Set default `transparency_url="https://fleetdm.com/transparency`
- Enable Fleet Premium licensees to set custom `transparency_url` via REST API and `fleetctl apply`
- Add `transparency_url` to `GET /device/{token}` endpoint response

View file

@ -2,6 +2,7 @@ package service
import (
"context"
"net/http"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
hostctx "github.com/fleetdm/fleet/v4/server/contexts/host"
@ -21,11 +22,10 @@ func (r *getDeviceHostRequest) deviceAuthToken() string {
}
type getDeviceHostResponse struct {
Host *HostDetailResponse `json:"host"`
OrgLogoURL string `json:"org_logo_url"`
TransparencyURL string `json:"transparency_url"`
Err error `json:"error,omitempty"`
License fleet.LicenseInfo `json:"license"`
Host *HostDetailResponse `json:"host"`
OrgLogoURL string `json:"org_logo_url"`
Err error `json:"error,omitempty"`
License fleet.LicenseInfo `json:"license"`
}
func (r getDeviceHostResponse) error() error { return r.Err }
@ -65,16 +65,10 @@ func getDeviceHostEndpoint(ctx context.Context, request interface{}, svc fleet.S
return getDeviceHostResponse{Err: err}, nil
}
transparencyURL := fleet.DefaultTransparencyURL
if license.Tier == "premium" && ac.FleetDesktop.TransparencyURL != "" {
transparencyURL = ac.FleetDesktop.TransparencyURL
}
return getDeviceHostResponse{
Host: resp,
OrgLogoURL: ac.OrgInfo.OrgLogoURL,
TransparencyURL: transparencyURL,
License: *license,
Host: resp,
OrgLogoURL: ac.OrgInfo.OrgLogoURL,
License: *license,
}, nil
}
@ -244,3 +238,47 @@ func (r deviceAPIFeaturesResponse) error() error { return r.Err }
func deviceAPIFeaturesEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
return deviceAPIFeaturesResponse{Features: fleet.DeviceAPIFeatures{}}, nil
}
////////////////////////////////////////////////////////////////////////////////
// Transparency URL Redirect
////////////////////////////////////////////////////////////////////////////////
type transparencyURLRequest struct {
Token string `url:"token"`
}
func (r *transparencyURLRequest) deviceAuthToken() string {
return r.Token
}
type transparencyURLResponse struct {
RedirectURL string `json:"-"` // used to control the redirect, see hijackRender method
Err error `json:"error,omitempty"`
}
func (r transparencyURLResponse) hijackRender(ctx context.Context, w http.ResponseWriter) {
w.Header().Set("Location", r.RedirectURL)
w.WriteHeader(http.StatusTemporaryRedirect)
}
func (r transparencyURLResponse) error() error { return r.Err }
func transparencyURL(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
config, err := svc.AppConfig(ctx)
if err != nil {
return transparencyURLResponse{Err: err}, nil
}
license, err := svc.License(ctx)
if err != nil {
return transparencyURLResponse{Err: err}, nil
}
transparencyURL := fleet.DefaultTransparencyURL
// Fleet Premium license is required for custom transparency url
if license.Tier == "premium" && config.FleetDesktop.TransparencyURL != "" {
transparencyURL = config.FleetDesktop.TransparencyURL
}
return transparencyURLResponse{RedirectURL: transparencyURL}, nil
}

View file

@ -401,6 +401,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
de.GET("/api/_version_/fleet/device/{token}/macadmins", getDeviceMacadminsDataEndpoint, getDeviceMacadminsDataRequest{})
de.GET("/api/_version_/fleet/device/{token}/policies", listDevicePoliciesEndpoint, listDevicePoliciesRequest{})
de.GET("/api/_version_/fleet/device/{token}/api_features", deviceAPIFeaturesEndpoint, deviceAPIFeaturesRequest{})
de.GET("/api/_version_/fleet/device/{token}/transparency", transparencyURL, transparencyURLRequest{})
// host-authenticated endpoints
he := newHostAuthenticatedEndpointer(svc, logger, opts, r, apiVersions...)

View file

@ -4766,12 +4766,12 @@ func (s *integrationTestSuite) TestDefaultTransparencyURL() {
require.Equal(t, fleet.DefaultTransparencyURL, acResp.FleetDesktop.TransparencyURL)
// confirm device endpoint returns initial default url
deviceResp := &getDeviceHostResponse{}
rawResp := s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK)
deviceResp := &transparencyURLResponse{}
rawResp := s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/transparency", nil, http.StatusTemporaryRedirect)
json.NewDecoder(rawResp.Body).Decode(deviceResp)
rawResp.Body.Close()
require.NoError(t, deviceResp.Err)
require.Equal(t, fleet.DefaultTransparencyURL, deviceResp.TransparencyURL)
require.Equal(t, fleet.DefaultTransparencyURL, rawResp.Header.Get("Location"))
// empty string applies default url
acResp = appConfigResponse{}
@ -4780,24 +4780,24 @@ func (s *integrationTestSuite) TestDefaultTransparencyURL() {
require.Equal(t, fleet.DefaultTransparencyURL, acResp.FleetDesktop.TransparencyURL)
// device endpoint returns default url
deviceResp = &getDeviceHostResponse{}
rawResp = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK)
deviceResp = &transparencyURLResponse{}
rawResp = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/transparency", nil, http.StatusTemporaryRedirect)
json.NewDecoder(rawResp.Body).Decode(deviceResp)
rawResp.Body.Close()
require.NoError(t, deviceResp.Err)
require.Equal(t, fleet.DefaultTransparencyURL, deviceResp.TransparencyURL)
require.Equal(t, fleet.DefaultTransparencyURL, rawResp.Header.Get("Location"))
// modify transparency url with custom url fails
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", fleet.AppConfig{FleetDesktop: fleet.FleetDesktopSettings{TransparencyURL: "customURL"}}, http.StatusUnprocessableEntity, &acResp)
// device endpoint still returns default url
deviceResp = &getDeviceHostResponse{}
rawResp = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK)
deviceResp = &transparencyURLResponse{}
rawResp = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/transparency", nil, http.StatusTemporaryRedirect)
json.NewDecoder(rawResp.Body).Decode(deviceResp)
rawResp.Body.Close()
require.NoError(t, deviceResp.Err)
require.Equal(t, fleet.DefaultTransparencyURL, deviceResp.TransparencyURL)
require.Equal(t, fleet.DefaultTransparencyURL, rawResp.Header.Get("Location"))
}
func (s *integrationTestSuite) TestModifyUser() {

View file

@ -1136,12 +1136,12 @@ func (s *integrationEnterpriseTestSuite) TestCustomTransparencyURL() {
require.Equal(t, fleet.DefaultTransparencyURL, acResp.FleetDesktop.TransparencyURL)
// confirm device endpoint returns initial default url
deviceResp := &getDeviceHostResponse{}
rawResp := s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK)
deviceResp := &transparencyURLResponse{}
rawResp := s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/transparency", nil, http.StatusTemporaryRedirect)
json.NewDecoder(rawResp.Body).Decode(deviceResp)
rawResp.Body.Close()
require.NoError(t, deviceResp.Err)
require.Equal(t, fleet.DefaultTransparencyURL, deviceResp.TransparencyURL)
require.Equal(t, fleet.DefaultTransparencyURL, rawResp.Header.Get("Location"))
// set custom url
acResp = appConfigResponse{}
@ -1150,12 +1150,12 @@ func (s *integrationEnterpriseTestSuite) TestCustomTransparencyURL() {
require.Equal(t, "customURL", acResp.FleetDesktop.TransparencyURL)
// device endpoint returns custom url
deviceResp = &getDeviceHostResponse{}
rawResp = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK)
deviceResp = &transparencyURLResponse{}
rawResp = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/transparency", nil, http.StatusTemporaryRedirect)
json.NewDecoder(rawResp.Body).Decode(deviceResp)
rawResp.Body.Close()
require.NoError(t, deviceResp.Err)
require.Equal(t, "customURL", deviceResp.TransparencyURL)
require.Equal(t, "customURL", rawResp.Header.Get("Location"))
// empty string applies default url
acResp = appConfigResponse{}
@ -1164,10 +1164,10 @@ func (s *integrationEnterpriseTestSuite) TestCustomTransparencyURL() {
require.Equal(t, fleet.DefaultTransparencyURL, acResp.FleetDesktop.TransparencyURL)
// device endpoint returns default url
deviceResp = &getDeviceHostResponse{}
rawResp = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK)
deviceResp = &transparencyURLResponse{}
rawResp = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/transparency", nil, http.StatusTemporaryRedirect)
json.NewDecoder(rawResp.Body).Decode(deviceResp)
rawResp.Body.Close()
require.NoError(t, deviceResp.Err)
require.Equal(t, fleet.DefaultTransparencyURL, deviceResp.TransparencyURL)
require.Equal(t, fleet.DefaultTransparencyURL, rawResp.Header.Get("Location"))
}