未验证 提交 70d0d5fe 编写于 作者: H huanggze

Allow global admins to view deleted namespace logs

Signed-off-by: Nhuanggze <loganhuang@yunify.com>
上级 659316da
......@@ -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,
}
decision, _, err := t.authorizer.Authorize(podLogs)
if err != nil {
klog.Error(err)
return nil, err
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
}
}
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,
}
decision, _, err := t.authorizer.Authorize(podLogs)
if err != nil {
klog.Error(err)
return err
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
}
}
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: ct,
},
},
},
},
{
Range: &Range{
Time: &Time{
Gte: func() *time.Time { t := sf.NamespaceFilter[ns]; return &t }(),
},
}
} 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 {
......
{
"query":{
"bool":{
"filter":[
{
"bool":{
"should":[
{
"bool":{
"filter":[
{
"match_phrase":{
"kubernetes.namespace_name.keyword":"default"
}
}
]
}
}
],
"minimum_should_match":1
}
}
]
}
}
}
\ No newline at end of file
......@@ -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
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册