1package vault 2 3import ( 4 "context" 5 "net/http" 6 "path" 7 "strings" 8 "time" 9 10 "github.com/hashicorp/vault/helper/timeutil" 11 "github.com/hashicorp/vault/sdk/framework" 12 "github.com/hashicorp/vault/sdk/logical" 13) 14 15// activityQueryPath is available in every namespace 16func (b *SystemBackend) activityQueryPath() *framework.Path { 17 return &framework.Path{ 18 Pattern: "internal/counters/activity$", 19 Fields: map[string]*framework.FieldSchema{ 20 "start_time": { 21 Type: framework.TypeTime, 22 Description: "Start of query interval", 23 }, 24 "end_time": { 25 Type: framework.TypeTime, 26 Description: "End of query interval", 27 }, 28 }, 29 HelpSynopsis: strings.TrimSpace(sysHelp["activity-query"][0]), 30 HelpDescription: strings.TrimSpace(sysHelp["activity-query"][1]), 31 32 Operations: map[logical.Operation]framework.OperationHandler{ 33 logical.ReadOperation: &framework.PathOperation{ 34 Callback: b.handleClientMetricQuery, 35 Summary: "Report the client count metrics, for this namespace and all child namespaces.", 36 }, 37 }, 38 } 39} 40 41// monthlyActivityCountPath is available in every namespace 42func (b *SystemBackend) monthlyActivityCountPath() *framework.Path { 43 return &framework.Path{ 44 Pattern: "internal/counters/activity/monthly", 45 HelpSynopsis: strings.TrimSpace(sysHelp["activity-monthly"][0]), 46 HelpDescription: strings.TrimSpace(sysHelp["activity-monthly"][1]), 47 Operations: map[logical.Operation]framework.OperationHandler{ 48 logical.ReadOperation: &framework.PathOperation{ 49 Callback: b.handleMonthlyActivityCount, 50 Summary: "Report the number of clients for this month, for this namespace and all child namespaces.", 51 }, 52 }, 53 } 54} 55 56// rootActivityPaths are available only in the root namespace 57func (b *SystemBackend) rootActivityPaths() []*framework.Path { 58 return []*framework.Path{ 59 b.activityQueryPath(), 60 b.monthlyActivityCountPath(), 61 { 62 Pattern: "internal/counters/config$", 63 Fields: map[string]*framework.FieldSchema{ 64 "default_report_months": { 65 Type: framework.TypeInt, 66 Default: 12, 67 Description: "Number of months to report if no start date specified.", 68 }, 69 "retention_months": { 70 Type: framework.TypeInt, 71 Default: 24, 72 Description: "Number of months of client data to retain. Setting to 0 will clear all existing data.", 73 }, 74 "enabled": { 75 Type: framework.TypeString, 76 Default: "default", 77 Description: "Enable or disable collection of client count: enable, disable, or default.", 78 }, 79 }, 80 HelpSynopsis: strings.TrimSpace(sysHelp["activity-config"][0]), 81 HelpDescription: strings.TrimSpace(sysHelp["activity-config"][1]), 82 Operations: map[logical.Operation]framework.OperationHandler{ 83 logical.ReadOperation: &framework.PathOperation{ 84 Callback: b.handleActivityConfigRead, 85 Summary: "Read the client count tracking configuration.", 86 }, 87 logical.UpdateOperation: &framework.PathOperation{ 88 Callback: b.handleActivityConfigUpdate, 89 Summary: "Enable or disable collection of client count, set retention period, or set default reporting period.", 90 }, 91 }, 92 }, 93 } 94} 95 96func (b *SystemBackend) handleClientMetricQuery(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 97 a := b.Core.activityLog 98 if a == nil { 99 return logical.ErrorResponse("no activity log present"), nil 100 } 101 102 startTime := d.Get("start_time").(time.Time) 103 endTime := d.Get("end_time").(time.Time) 104 105 // If a specific endTime is used, then respect that 106 // otherwise we want to give the latest N months, so go back to the start 107 // of the previous month 108 // 109 // Also convert any user inputs to UTC to avoid 110 // problems later. 111 if endTime.IsZero() { 112 endTime = timeutil.EndOfMonth(timeutil.StartOfPreviousMonth(time.Now().UTC())) 113 } else { 114 endTime = endTime.UTC() 115 } 116 if startTime.IsZero() { 117 startTime = a.DefaultStartTime(endTime) 118 } else { 119 startTime = startTime.UTC() 120 } 121 if startTime.After(endTime) { 122 return logical.ErrorResponse("start_time is later than end_time"), nil 123 } 124 125 results, err := a.handleQuery(ctx, startTime, endTime) 126 if err != nil { 127 return nil, err 128 } 129 if results == nil { 130 resp204, err := logical.RespondWithStatusCode(nil, req, http.StatusNoContent) 131 return resp204, err 132 } 133 134 return &logical.Response{ 135 Data: results, 136 }, nil 137} 138 139func (b *SystemBackend) handleMonthlyActivityCount(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 140 a := b.Core.activityLog 141 if a == nil { 142 return logical.ErrorResponse("no activity log present"), nil 143 } 144 145 results := a.partialMonthClientCount(ctx) 146 if results == nil { 147 return logical.RespondWithStatusCode(nil, req, http.StatusNoContent) 148 } 149 150 return &logical.Response{ 151 Data: results, 152 }, nil 153} 154 155func (b *SystemBackend) handleActivityConfigRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 156 a := b.Core.activityLog 157 if a == nil { 158 return logical.ErrorResponse("no activity log present"), nil 159 } 160 161 config, err := a.loadConfigOrDefault(ctx) 162 if err != nil { 163 return nil, err 164 } 165 166 qa, err := a.queriesAvailable(ctx) 167 if err != nil { 168 return nil, err 169 } 170 171 if config.Enabled == "default" { 172 config.Enabled = activityLogEnabledDefaultValue 173 } 174 175 return &logical.Response{ 176 Data: map[string]interface{}{ 177 "default_report_months": config.DefaultReportMonths, 178 "retention_months": config.RetentionMonths, 179 "enabled": config.Enabled, 180 "queries_available": qa, 181 }, 182 }, nil 183} 184 185func (b *SystemBackend) handleActivityConfigUpdate(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 186 a := b.Core.activityLog 187 if a == nil { 188 return logical.ErrorResponse("no activity log present"), nil 189 } 190 191 warnings := make([]string, 0) 192 193 config, err := a.loadConfigOrDefault(ctx) 194 if err != nil { 195 return nil, err 196 } 197 198 { 199 // Parse the default report months 200 if defaultReportMonthsRaw, ok := d.GetOk("default_report_months"); ok { 201 config.DefaultReportMonths = defaultReportMonthsRaw.(int) 202 } 203 204 if config.DefaultReportMonths <= 0 { 205 return logical.ErrorResponse("default_report_months must be greater than 0"), logical.ErrInvalidRequest 206 } 207 } 208 209 { 210 // Parse the retention months 211 if retentionMonthsRaw, ok := d.GetOk("retention_months"); ok { 212 config.RetentionMonths = retentionMonthsRaw.(int) 213 } 214 215 if config.RetentionMonths < 0 { 216 return logical.ErrorResponse("retention_months must be greater than or equal to 0"), logical.ErrInvalidRequest 217 } 218 } 219 220 { 221 // Parse the enabled setting 222 if enabledRaw, ok := d.GetOk("enabled"); ok { 223 enabledStr := enabledRaw.(string) 224 225 // If we switch from enabled to disabled, then we return a warning to the client. 226 // We have to keep the default state of activity log enabled in mind 227 if config.Enabled == "enable" && enabledStr == "disable" || 228 !activityLogEnabledDefault && config.Enabled == "enable" && enabledStr == "default" || 229 activityLogEnabledDefault && config.Enabled == "default" && enabledStr == "disable" { 230 warnings = append(warnings, "the current monthly segment will be deleted because the activity log was disabled") 231 } 232 233 switch enabledStr { 234 case "default", "enable", "disable": 235 config.Enabled = enabledStr 236 default: 237 return logical.ErrorResponse("enabled must be one of \"default\", \"enable\", \"disable\""), logical.ErrInvalidRequest 238 } 239 } 240 } 241 242 enabled := config.Enabled == "enable" 243 if !enabled && config.Enabled == "default" { 244 enabled = activityLogEnabledDefault 245 } 246 247 if enabled && config.RetentionMonths == 0 { 248 return logical.ErrorResponse("retention_months cannot be 0 while enabled"), logical.ErrInvalidRequest 249 } 250 251 // Store the config 252 entry, err := logical.StorageEntryJSON(path.Join(activitySubPath, activityConfigKey), config) 253 if err != nil { 254 return nil, err 255 } 256 if err := req.Storage.Put(ctx, entry); err != nil { 257 return nil, err 258 } 259 260 // Set the new config on the activity log 261 a.SetConfig(ctx, config) 262 263 if len(warnings) > 0 { 264 return &logical.Response{ 265 Warnings: warnings, 266 }, nil 267 } 268 269 return nil, nil 270} 271