pkg/controller/elasticsearch/user/roles.go (304 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.
package user
import (
"fmt"
"gopkg.in/yaml.v3"
"k8s.io/utils/ptr"
beatv1beta1 "github.com/elastic/cloud-on-k8s/v3/pkg/apis/beat/v1beta1"
esclient "github.com/elastic/cloud-on-k8s/v3/pkg/controller/elasticsearch/client"
)
const (
// RolesFile is the name of the roles file in the ES config dir.
RolesFile = "roles.yml"
// SuperUserBuiltinRole is the name of the built-in superuser role.
SuperUserBuiltinRole = "superuser"
// ClusterManageRole is the name of a custom role to manage the cluster.
ClusterManageRole = "elastic-internal_cluster_manage"
// ProbeUserRole is the name of the role used by the internal probe user.
ProbeUserRole = "elastic_internal_probe_user"
// RemoteMonitoringCollectorBuiltinRole is the name of the built-in remote_monitoring_collector role.
RemoteMonitoringCollectorBuiltinRole = "remote_monitoring_collector"
// DiagnosticsUserRoleV80 is the name of the built-in role for ECK diagnostics use from version 8.0 to 8.4.
DiagnosticsUserRoleV80 = "elastic_internal_diagnostics_v80"
// DiagnosticsUserRoleV85 is the name of the built-in role for ECK diagnostics use from version 8.5.
DiagnosticsUserRoleV85 = "elastic_internal_diagnostics_v85"
// ApmUserRoleV6 is the name of the role used by 6.8.x APMServer instances to connect to Elasticsearch.
ApmUserRoleV6 = "eck_apm_user_role_v6"
// ApmUserRoleV7 is the name of the role used by APMServer instances to connect to Elasticsearch from version 7.1 to 7.4 included.
ApmUserRoleV7 = "eck_apm_user_role_v7"
// ApmUserRoleV75 is the name of the role used by APMServer instances to connect to Elasticsearch from version 7.5
ApmUserRoleV75 = "eck_apm_user_role_v75"
// ApmUserRoleV80 is the name of the role used by APMServer instances to connect to Elasticsearch from version 8.0
ApmUserRoleV80 = "eck_apm_user_role_v80"
// ApmUserRoleV87 is the name of the role used by APMServer instances to connect to Elasticsearch from version 8.7
ApmUserRoleV87 = "eck_apm_user_role_v87"
// ApmAgentUserRole is the name of the role used by APMServer instances to connect to Kibana
ApmAgentUserRole = "eck_apm_agent_user_role"
// StackMonitoringMetricsUserRole is the name of the role used by Metricbeat and Filebeat to send metrics and log
// data to the monitoring Elasticsearch cluster when Stack Monitoring is enabled
StackMonitoringUserRole = "eck_stack_mon_user_role"
FleetAdminUserRole = "eck_fleet_admin_user_role"
LogstashUserRole = "eck_logstash_user_role"
// V70 indicates version 7.0
V70 = "v70"
// V73 indicates version 7.3
V73 = "v73"
// V75 indicates version 7.5
V75 = "v75"
// V77 indicates version 7.7
V77 = "v77"
)
var (
diagnosticsRoleIndices = []esclient.IndexRole{
{
Names: []string{"*"},
Privileges: []string{"monitor", "read", "view_index_metadata"},
AllowRestrictedIndices: ptr.To[bool](true),
},
}
diagnosticsAppsKibanaPrivileges = []esclient.ApplicationRole{
{
Application: "kibana-.kibana",
Resources: []string{"*"},
Privileges: []string{
"feature_ml.read",
"feature_siem.read",
"feature_siem.read_alerts",
"feature_siem.policy_management_read",
"feature_siem.endpoint_list_read",
"feature_siem.trusted_applications_read",
"feature_siem.event_filters_read",
"feature_siem.host_isolation_exceptions_read",
"feature_siem.blocklist_read",
"feature_siem.actions_log_management_read",
"feature_securitySolutionCases.read",
"feature_securitySolutionAssistant.read",
"feature_actions.read",
"feature_builtInAlerts.read",
"feature_fleet.all",
"feature_fleetv2.all",
"feature_osquery.read",
"feature_indexPatterns.read",
"feature_discover.read",
"feature_dashboard.read",
"feature_maps.read",
"feature_visualize.read",
},
},
}
// PredefinedRoles to create for internal needs.
PredefinedRoles = RolesFileContent{
ProbeUserRole: esclient.Role{Cluster: []string{"monitor"}},
ClusterManageRole: esclient.Role{Cluster: []string{"manage"}},
DiagnosticsUserRoleV80: esclient.Role{
Cluster: []string{"monitor", "monitor_snapshot", "manage", "read_ilm", "manage_security"},
Indices: diagnosticsRoleIndices,
Applications: diagnosticsAppsKibanaPrivileges,
},
DiagnosticsUserRoleV85: esclient.Role{
Cluster: []string{"monitor", "monitor_snapshot", "manage", "read_ilm", "read_security"},
Indices: diagnosticsRoleIndices,
Applications: diagnosticsAppsKibanaPrivileges,
},
ApmUserRoleV6: esclient.Role{
Cluster: []string{"monitor", "manage_index_templates"},
Indices: []esclient.IndexRole{
{
Names: []string{"apm-*"},
Privileges: []string{"write", "create_index"},
},
},
},
ApmUserRoleV7: esclient.Role{
Cluster: []string{"monitor", "manage_ilm", "manage_index_templates"},
Indices: []esclient.IndexRole{
{
Names: []string{"apm-*"},
Privileges: []string{"manage", "write", "create_index"},
},
},
},
ApmUserRoleV75: esclient.Role{
Cluster: []string{"monitor", "manage_ilm", "manage_api_key"}, // manage_api_key has been introduced in 7.5
Indices: []esclient.IndexRole{
{
Names: []string{"apm-*"},
Privileges: []string{"manage", "create_doc", "create_index"},
},
},
},
ApmUserRoleV80: esclient.Role{
Cluster: []string{"cluster:monitor/main", "manage_index_templates"},
Indices: []esclient.IndexRole{
{
Names: []string{"traces-apm*", "metrics-apm*", "logs-apm*"},
Privileges: []string{"auto_configure", "create_doc"},
},
{
Names: []string{"traces-apm.sampled-*"},
Privileges: []string{"maintenance", "monitor", "read"},
},
},
},
ApmUserRoleV87: esclient.Role{
Cluster: []string{"cluster:monitor/main", "manage_index_templates"},
Indices: []esclient.IndexRole{
{
Names: []string{"traces-apm*", "metrics-apm*", "logs-apm*"},
Privileges: []string{"auto_configure", "create_doc"},
},
{
Names: []string{"traces-apm.sampled-*"},
Privileges: []string{"maintenance", "monitor", "read"},
},
{
Names: []string{".apm-agent-configuration", ".apm-source-map"},
Privileges: []string{"read"},
AllowRestrictedIndices: func() *bool {
allow := true
return &allow
}(),
},
},
},
ApmAgentUserRole: esclient.Role{
Cluster: []string{},
Indices: []esclient.IndexRole{},
Applications: []esclient.ApplicationRole{
{
Application: "kibana-.kibana",
Resources: []string{"space:default"},
Privileges: []string{"feature_apm.read"},
},
},
},
// StackMonitoringUserRole is a dedicated role for Stack Monitoring with Metricbeat and Filebeat used for the
// user sending monitoring data.
// See: https://www.elastic.co/guide/en/beats/filebeat/7.14/privileges-to-publish-monitoring.html.
StackMonitoringUserRole: esclient.Role{
Cluster: []string{
"monitor",
"manage_index_templates",
"manage_ingest_pipelines",
"manage_ilm",
"read_ilm",
"cluster:admin/xpack/watcher/watch/put",
"cluster:admin/xpack/watcher/watch/delete",
},
Indices: []esclient.IndexRole{
{
Names: []string{".monitoring-*"},
Privileges: []string{"all"},
},
{
Names: []string{"metricbeat-*"},
Privileges: []string{"manage", "read", "create_doc", "view_index_metadata", "create_index"},
},
{
Names: []string{"filebeat-*"},
Privileges: []string{"manage", "read", "create_doc", "view_index_metadata", "create_index"},
},
},
},
FleetAdminUserRole: esclient.Role{
Applications: []esclient.ApplicationRole{
{
Application: "kibana-.kibana",
Resources: []string{"*"},
Privileges: []string{"feature_fleet.all", "feature_fleetv2.all"},
},
},
},
LogstashUserRole: esclient.Role{
Cluster: []string{
"monitor",
"manage_ilm",
"read_ilm",
"manage_logstash_pipelines",
"manage_index_templates",
"cluster:admin/ingest/pipeline/get",
},
Indices: []esclient.IndexRole{
{
Names: []string{"logstash", "logstash-*", "ecs-logstash", "ecs-logstash-*", "logs-*", "metrics-*", "synthetics-*", "traces-*"},
Privileges: []string{"manage", "write", "create_index", "read", "view_index_metadata"},
},
},
},
}
// Additional index permissions for Beats users
BeatsAdditionalPermissions = map[string]string{
"filebeat": "logs-*",
"metricbeat": "metrics-*",
"packetbeat": "logs-*",
"auditbeat": "logs-*",
"heartbeat": "synthetics-*",
}
)
func init() {
for beat := range beatv1beta1.KnownTypes {
PredefinedRoles[BeatEsRoleName(V77, beat)] = esclient.Role{
Cluster: []string{"monitor", "manage_ilm", "manage_ml", "read_ilm", "cluster:admin/ingest/pipeline/get"},
Indices: []esclient.IndexRole{
{
Names: append([]string{fmt.Sprintf("%s-*", beat)}, BeatsAdditionalPermissions[beat]),
Privileges: []string{"manage", "read", "create_doc", "view_index_metadata", "create_index"},
},
},
}
PredefinedRoles[BeatEsRoleName(V75, beat)] = esclient.Role{
Cluster: []string{"monitor", "manage_ilm", "manage_ml", "read_ilm", "cluster:admin/ingest/pipeline/get"},
Indices: []esclient.IndexRole{
{
Names: append([]string{fmt.Sprintf("%s-*", beat)}, BeatsAdditionalPermissions[beat]),
Privileges: []string{"manage", "read", "create_doc", "view_index_metadata", "create_index"},
},
},
}
PredefinedRoles[BeatEsRoleName(V73, beat)] = esclient.Role{
Cluster: []string{"monitor", "manage_ilm", "manage_ml", "read_ilm", "manage_pipeline"},
Indices: []esclient.IndexRole{
{
Names: append([]string{fmt.Sprintf("%s-*", beat)}, BeatsAdditionalPermissions[beat]),
Privileges: []string{"manage", "read", "index", "view_index_metadata", "create_index"},
},
},
}
PredefinedRoles[BeatEsRoleName(V70, beat)] = esclient.Role{
Cluster: []string{"manage_index_templates", "monitor", "manage_ilm", "manage_ml", "manage_pipeline"},
Indices: []esclient.IndexRole{
{
Names: append([]string{fmt.Sprintf("%s-*", beat)}, BeatsAdditionalPermissions[beat]),
Privileges: []string{"manage", "read", "index", "create_index"},
},
},
}
PredefinedRoles[BeatKibanaRoleName(V77, beat)] = esclient.Role{
Cluster: []string{"monitor", "manage_ilm", "manage_ml"},
Indices: []esclient.IndexRole{
{
Names: append([]string{fmt.Sprintf("%s-*", beat)}, BeatsAdditionalPermissions[beat]),
Privileges: []string{"manage", "read"},
},
},
}
PredefinedRoles[BeatKibanaRoleName(V73, beat)] = esclient.Role{
Cluster: []string{"monitor", "manage_ilm", "manage_ml"},
Indices: []esclient.IndexRole{
{
Names: append([]string{fmt.Sprintf("%s-*", beat)}, BeatsAdditionalPermissions[beat]),
Privileges: []string{"manage", "read"},
},
},
}
PredefinedRoles[BeatKibanaRoleName(V70, beat)] = esclient.Role{
Cluster: []string{"manage_index_templates", "monitor", "manage_ilm", "manage_ml"},
Indices: []esclient.IndexRole{
{
Names: append([]string{fmt.Sprintf("%s-*", beat)}, BeatsAdditionalPermissions[beat]),
Privileges: []string{"manage", "read"},
},
},
}
}
}
func BeatEsRoleName(version, beatType string) string {
return fmt.Sprintf("eck_beat_es_%s_role_%s", beatType, version)
}
func BeatKibanaRoleName(version, beatType string) string {
return fmt.Sprintf("eck_beat_kibana_%s_role_%s", beatType, version)
}
// RolesFileContent is a map {role name -> yaml role spec}.
// We care about the role names here, but consider the roles spec as a yaml blob we don't need to access.
type RolesFileContent map[string]interface{}
// parseRolesFileContent returns a RolesFileContent from the given data.
// Since rolesFileContent already corresponds to a deserialized yaml representation of the roles files,
// we just unmarshal from the yaml data.
func parseRolesFileContent(data []byte) (RolesFileContent, error) {
var parsed RolesFileContent
err := yaml.Unmarshal(data, &parsed)
return parsed, err
}
// fileBytes returns the file representation of rolesFileContent.
// Since rolesFileContent already corresponds to a deserialized yaml representation of the roles files,
// we just marshal it back to yaml.
func (r RolesFileContent) FileBytes() ([]byte, error) {
return yaml.Marshal(&r)
}
// mergeWith merges multiple rolesFileContent, giving priority to other returning a new RolesFileContent.
func (r RolesFileContent) MergeWith(other RolesFileContent) RolesFileContent {
result := make(RolesFileContent)
for roleName, roleSpec := range r {
result[roleName] = roleSpec
}
for roleName, roleSpec := range other {
result[roleName] = roleSpec
}
return result
}