Merge pull request #2688 from huanggze/master
Allow global admins to view deleted namespace logs
This commit is contained in:
@@ -27,8 +27,6 @@ type APIResponse struct {
|
||||
|
||||
type Query struct {
|
||||
Operation string
|
||||
WorkspaceFilter string
|
||||
WorkspaceSearch string
|
||||
NamespaceFilter string
|
||||
NamespaceSearch string
|
||||
WorkloadFilter string
|
||||
@@ -49,8 +47,6 @@ type Query struct {
|
||||
func ParseQueryParameter(req *restful.Request) (*Query, error) {
|
||||
var q Query
|
||||
q.Operation = req.QueryParameter("operation")
|
||||
q.WorkspaceFilter = req.QueryParameter("workspaces")
|
||||
q.WorkspaceSearch = req.QueryParameter("workspace_query")
|
||||
q.NamespaceFilter = req.QueryParameter("namespaces")
|
||||
q.NamespaceSearch = req.QueryParameter("namespace_query")
|
||||
q.WorkloadFilter = req.QueryParameter("workloads")
|
||||
|
||||
@@ -192,8 +192,6 @@ func AddToContainer(c *restful.Container, factory informers.InformerFactory, k8s
|
||||
To(handler.QueryLogs).
|
||||
Doc("Query logs against the cluster.").
|
||||
Param(ws.QueryParameter("operation", "Operation type. This can be one of four types: query (for querying logs), statistics (for retrieving statistical data), histogram (for displaying log count by time interval) and export (for exporting logs). Defaults to query.").DefaultValue("query").DataType("string").Required(false)).
|
||||
Param(ws.QueryParameter("workspaces", "A comma-separated list of workspaces. This field restricts the query to specified workspaces. For example, the following filter matches the workspace my-ws and demo-ws: `my-ws,demo-ws`").DataType("string").Required(false)).
|
||||
Param(ws.QueryParameter("workspace_query", "A comma-separated list of keywords. Differing from **workspaces**, this field performs fuzzy matching on workspaces. For example, the following value limits the query to workspaces whose name contains the word my(My,MY,...) *OR* demo(Demo,DemO,...): `my,demo`.").DataType("string").Required(false)).
|
||||
Param(ws.QueryParameter("namespaces", "A comma-separated list of namespaces. This field restricts the query to specified namespaces. For example, the following filter matches the namespace my-ns and demo-ns: `my-ns,demo-ns`").DataType("string").Required(false)).
|
||||
Param(ws.QueryParameter("namespace_query", "A comma-separated list of keywords. Differing from **namespaces**, this field performs fuzzy matching on namespaces. For example, the following value limits the query to namespaces whose name contains the word my(My,MY,...) *OR* demo(Demo,DemO,...): `my,demo`.").DataType("string").Required(false)).
|
||||
Param(ws.QueryParameter("workloads", "A comma-separated list of workloads. This field restricts the query to specified workloads. For example, the following filter matches the workload my-wl and demo-wl: `my-wl,demo-wl`").DataType("string").Required(false)).
|
||||
|
||||
@@ -563,6 +563,7 @@ func (t *tenantOperator) DeleteWorkspace(workspace string) error {
|
||||
// 2. If `workspaceSubstrs` is not empty, the namespace SHOULD belong to a workspace whose name contains one of the specified substrings.
|
||||
// 3. If `namespaces` is not empty, the namespace SHOULD be one of the specified namespacs.
|
||||
// 4. If `namespaceSubstrs` is not empty, the namespace's name SHOULD contain one of the specified substrings.
|
||||
// 5. If ALL of the filters above are empty, returns all namespaces.
|
||||
func (t *tenantOperator) listIntersectedNamespaces(workspaces, workspaceSubstrs,
|
||||
namespaces, namespaceSubstrs []string) ([]*corev1.Namespace, error) {
|
||||
var (
|
||||
@@ -670,9 +671,7 @@ func (t *tenantOperator) Events(user user.Info, queryParam *eventsv1alpha1.Query
|
||||
}
|
||||
|
||||
func (t *tenantOperator) QueryLogs(user user.Info, query *loggingv1alpha2.Query) (*loggingv1alpha2.APIResponse, error) {
|
||||
iNamespaces, err := t.listIntersectedNamespaces(
|
||||
stringutils.Split(query.WorkspaceFilter, ","),
|
||||
stringutils.Split(query.WorkspaceSearch, ","),
|
||||
iNamespaces, err := t.listIntersectedNamespaces(nil, nil,
|
||||
stringutils.Split(query.NamespaceFilter, ","),
|
||||
stringutils.Split(query.NamespaceSearch, ","))
|
||||
if err != nil {
|
||||
@@ -680,26 +679,57 @@ func (t *tenantOperator) QueryLogs(user user.Info, query *loggingv1alpha2.Query)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
namespaceCreateTimeMap := make(map[string]time.Time)
|
||||
for _, ns := range iNamespaces {
|
||||
podLogs := authorizer.AttributesRecord{
|
||||
User: user,
|
||||
Verb: "get",
|
||||
APIGroup: "",
|
||||
APIVersion: "v1",
|
||||
Namespace: ns.Name,
|
||||
Resource: "pods",
|
||||
Subresource: "log",
|
||||
ResourceRequest: true,
|
||||
ResourceScope: request.NamespaceScope,
|
||||
namespaceCreateTimeMap := make(map[string]*time.Time)
|
||||
|
||||
var isGlobalAdmin bool
|
||||
|
||||
// If it is a global admin, the user can view logs from any namespace.
|
||||
podLogs := authorizer.AttributesRecord{
|
||||
User: user,
|
||||
Verb: "get",
|
||||
APIGroup: "",
|
||||
APIVersion: "v1",
|
||||
Resource: "pods",
|
||||
Subresource: "log",
|
||||
ResourceRequest: true,
|
||||
ResourceScope: request.ClusterScope,
|
||||
}
|
||||
decision, _, err := t.authorizer.Authorize(podLogs)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
if decision == authorizer.DecisionAllow {
|
||||
isGlobalAdmin = true
|
||||
if query.NamespaceFilter != "" || query.NamespaceSearch != "" {
|
||||
for _, ns := range iNamespaces {
|
||||
namespaceCreateTimeMap[ns.Name] = nil
|
||||
}
|
||||
}
|
||||
decision, _, err := t.authorizer.Authorize(podLogs)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
if decision == authorizer.DecisionAllow {
|
||||
namespaceCreateTimeMap[ns.Name] = ns.CreationTimestamp.Time
|
||||
}
|
||||
|
||||
// If it is a regular user, this user can only view logs of namespaces the user belongs to.
|
||||
if !isGlobalAdmin {
|
||||
for _, ns := range iNamespaces {
|
||||
podLogs := authorizer.AttributesRecord{
|
||||
User: user,
|
||||
Verb: "get",
|
||||
APIGroup: "",
|
||||
APIVersion: "v1",
|
||||
Namespace: ns.Name,
|
||||
Resource: "pods",
|
||||
Subresource: "log",
|
||||
ResourceRequest: true,
|
||||
ResourceScope: request.NamespaceScope,
|
||||
}
|
||||
decision, _, err := t.authorizer.Authorize(podLogs)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
if decision == authorizer.DecisionAllow {
|
||||
namespaceCreateTimeMap[ns.Name] = &ns.CreationTimestamp.Time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -717,21 +747,24 @@ func (t *tenantOperator) QueryLogs(user user.Info, query *loggingv1alpha2.Query)
|
||||
}
|
||||
|
||||
var ar loggingv1alpha2.APIResponse
|
||||
noHit := !isGlobalAdmin && len(namespaceCreateTimeMap) == 0 ||
|
||||
isGlobalAdmin && len(namespaceCreateTimeMap) == 0 && (query.NamespaceFilter != "" || query.NamespaceSearch != "")
|
||||
|
||||
switch query.Operation {
|
||||
case loggingv1alpha2.OperationStatistics:
|
||||
if len(namespaceCreateTimeMap) == 0 {
|
||||
if noHit {
|
||||
ar.Statistics = &loggingclient.Statistics{}
|
||||
} else {
|
||||
ar, err = t.lo.GetCurrentStats(sf)
|
||||
}
|
||||
case loggingv1alpha2.OperationHistogram:
|
||||
if len(namespaceCreateTimeMap) == 0 {
|
||||
if noHit {
|
||||
ar.Histogram = &loggingclient.Histogram{}
|
||||
} else {
|
||||
ar, err = t.lo.CountLogsByInterval(sf, query.Interval)
|
||||
}
|
||||
default:
|
||||
if len(namespaceCreateTimeMap) == 0 {
|
||||
if noHit {
|
||||
ar.Logs = &loggingclient.Logs{}
|
||||
} else {
|
||||
ar, err = t.lo.SearchLogs(sf, query.From, query.Size, query.Sort)
|
||||
@@ -741,9 +774,7 @@ func (t *tenantOperator) QueryLogs(user user.Info, query *loggingv1alpha2.Query)
|
||||
}
|
||||
|
||||
func (t *tenantOperator) ExportLogs(user user.Info, query *loggingv1alpha2.Query, writer io.Writer) error {
|
||||
iNamespaces, err := t.listIntersectedNamespaces(
|
||||
stringutils.Split(query.WorkspaceFilter, ","),
|
||||
stringutils.Split(query.WorkspaceSearch, ","),
|
||||
iNamespaces, err := t.listIntersectedNamespaces(nil, nil,
|
||||
stringutils.Split(query.NamespaceFilter, ","),
|
||||
stringutils.Split(query.NamespaceSearch, ","))
|
||||
if err != nil {
|
||||
@@ -751,26 +782,57 @@ func (t *tenantOperator) ExportLogs(user user.Info, query *loggingv1alpha2.Query
|
||||
return err
|
||||
}
|
||||
|
||||
namespaceCreateTimeMap := make(map[string]time.Time)
|
||||
for _, ns := range iNamespaces {
|
||||
podLogs := authorizer.AttributesRecord{
|
||||
User: user,
|
||||
Verb: "get",
|
||||
APIGroup: "",
|
||||
APIVersion: "v1",
|
||||
Namespace: ns.Name,
|
||||
Resource: "pods",
|
||||
Subresource: "log",
|
||||
ResourceRequest: true,
|
||||
ResourceScope: request.NamespaceScope,
|
||||
namespaceCreateTimeMap := make(map[string]*time.Time)
|
||||
|
||||
var isGlobalAdmin bool
|
||||
|
||||
// If it is a global admin, the user can view logs from any namespace.
|
||||
podLogs := authorizer.AttributesRecord{
|
||||
User: user,
|
||||
Verb: "get",
|
||||
APIGroup: "",
|
||||
APIVersion: "v1",
|
||||
Resource: "pods",
|
||||
Subresource: "log",
|
||||
ResourceRequest: true,
|
||||
ResourceScope: request.ClusterScope,
|
||||
}
|
||||
decision, _, err := t.authorizer.Authorize(podLogs)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
if decision == authorizer.DecisionAllow {
|
||||
isGlobalAdmin = true
|
||||
if query.NamespaceFilter != "" || query.NamespaceSearch != "" {
|
||||
for _, ns := range iNamespaces {
|
||||
namespaceCreateTimeMap[ns.Name] = nil
|
||||
}
|
||||
}
|
||||
decision, _, err := t.authorizer.Authorize(podLogs)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
if decision == authorizer.DecisionAllow {
|
||||
namespaceCreateTimeMap[ns.Name] = ns.CreationTimestamp.Time
|
||||
}
|
||||
|
||||
// If it is a regular user, this user can only view logs of namespaces the user belongs to.
|
||||
if !isGlobalAdmin {
|
||||
for _, ns := range iNamespaces {
|
||||
podLogs := authorizer.AttributesRecord{
|
||||
User: user,
|
||||
Verb: "get",
|
||||
APIGroup: "",
|
||||
APIVersion: "v1",
|
||||
Namespace: ns.Name,
|
||||
Resource: "pods",
|
||||
Subresource: "log",
|
||||
ResourceRequest: true,
|
||||
ResourceScope: request.NamespaceScope,
|
||||
}
|
||||
decision, _, err := t.authorizer.Authorize(podLogs)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
if decision == authorizer.DecisionAllow {
|
||||
namespaceCreateTimeMap[ns.Name] = &ns.CreationTimestamp.Time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -787,7 +849,10 @@ func (t *tenantOperator) ExportLogs(user user.Info, query *loggingv1alpha2.Query
|
||||
Endtime: query.EndTime,
|
||||
}
|
||||
|
||||
if len(namespaceCreateTimeMap) == 0 {
|
||||
noHit := !isGlobalAdmin && len(namespaceCreateTimeMap) == 0 ||
|
||||
isGlobalAdmin && len(namespaceCreateTimeMap) == 0 && (query.NamespaceFilter != "" || query.NamespaceSearch != "")
|
||||
|
||||
if noHit {
|
||||
return nil
|
||||
} else {
|
||||
return t.lo.ExportLogs(sf, writer)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"github.com/json-iterator/go"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/logging"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -36,23 +35,38 @@ func (bb *bodyBuilder) mainBool(sf logging.SearchFilter) *bodyBuilder {
|
||||
if len(sf.NamespaceFilter) != 0 {
|
||||
var b Bool
|
||||
for ns := range sf.NamespaceFilter {
|
||||
match := Match{
|
||||
Bool: &Bool{
|
||||
Filter: []Match{
|
||||
{
|
||||
MatchPhrase: map[string]string{
|
||||
"kubernetes.namespace_name.keyword": ns,
|
||||
var match Match
|
||||
if ct := sf.NamespaceFilter[ns]; ct != nil {
|
||||
match = Match{
|
||||
Bool: &Bool{
|
||||
Filter: []Match{
|
||||
{
|
||||
MatchPhrase: map[string]string{
|
||||
"kubernetes.namespace_name.keyword": ns,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Range: &Range{
|
||||
Time: &Time{
|
||||
Gte: func() *time.Time { t := sf.NamespaceFilter[ns]; return &t }(),
|
||||
{
|
||||
Range: &Range{
|
||||
Time: &Time{
|
||||
Gte: ct,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
match = Match{
|
||||
Bool: &Bool{
|
||||
Filter: []Match{
|
||||
{
|
||||
MatchPhrase: map[string]string{
|
||||
"kubernetes.namespace_name.keyword": ns,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
b.Should = append(b.Should, match)
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ func TestMainBool(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
filter: logging.SearchFilter{
|
||||
NamespaceFilter: map[string]time.Time{
|
||||
"default": time.Unix(1589981934, 0),
|
||||
NamespaceFilter: map[string]*time.Time{
|
||||
"default": func() *time.Time { t := time.Unix(1589981934, 0); return &t }(),
|
||||
},
|
||||
},
|
||||
expected: "api_body_1.json",
|
||||
@@ -51,6 +51,14 @@ func TestMainBool(t *testing.T) {
|
||||
},
|
||||
expected: "api_body_7.json",
|
||||
},
|
||||
{
|
||||
filter: logging.SearchFilter{
|
||||
NamespaceFilter: map[string]*time.Time{
|
||||
"default": nil,
|
||||
},
|
||||
},
|
||||
expected: "api_body_8.json",
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
|
||||
26
pkg/simple/client/logging/elasticsearch/testdata/api_body_8.json
vendored
Normal file
26
pkg/simple/client/logging/elasticsearch/testdata/api_body_8.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"query":{
|
||||
"bool":{
|
||||
"filter":[
|
||||
{
|
||||
"bool":{
|
||||
"should":[
|
||||
{
|
||||
"bool":{
|
||||
"filter":[
|
||||
{
|
||||
"match_phrase":{
|
||||
"kubernetes.namespace_name.keyword":"default"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"minimum_should_match":1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ type SearchFilter struct {
|
||||
// To prevent disclosing archived logs of a reopened namespace,
|
||||
// NamespaceFilter records the namespace creation time.
|
||||
// Any query to this namespace must begin after its creation.
|
||||
NamespaceFilter map[string]time.Time
|
||||
NamespaceFilter map[string]*time.Time
|
||||
WorkloadSearch []string
|
||||
WorkloadFilter []string
|
||||
PodSearch []string
|
||||
|
||||
Reference in New Issue
Block a user