cloudwatch.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. package cloudwatch
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "io/ioutil"
  6. "strings"
  7. "sync"
  8. "time"
  9. "github.com/aws/aws-sdk-go/aws"
  10. "github.com/aws/aws-sdk-go/aws/awsutil"
  11. "github.com/aws/aws-sdk-go/aws/credentials"
  12. "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
  13. "github.com/aws/aws-sdk-go/aws/ec2metadata"
  14. "github.com/aws/aws-sdk-go/aws/session"
  15. "github.com/aws/aws-sdk-go/service/cloudwatch"
  16. "github.com/aws/aws-sdk-go/service/ec2"
  17. "github.com/aws/aws-sdk-go/service/sts"
  18. "github.com/grafana/grafana/pkg/log"
  19. "github.com/grafana/grafana/pkg/middleware"
  20. m "github.com/grafana/grafana/pkg/models"
  21. )
  22. type actionHandler func(*cwRequest, *middleware.Context)
  23. var actionHandlers map[string]actionHandler
  24. type cwRequest struct {
  25. Region string `json:"region"`
  26. Action string `json:"action"`
  27. Body []byte `json:"-"`
  28. DataSource *m.DataSource
  29. }
  30. func init() {
  31. actionHandlers = map[string]actionHandler{
  32. "GetMetricStatistics": handleGetMetricStatistics,
  33. "ListMetrics": handleListMetrics,
  34. "DescribeAlarms": handleDescribeAlarms,
  35. "DescribeAlarmsForMetric": handleDescribeAlarmsForMetric,
  36. "DescribeAlarmHistory": handleDescribeAlarmHistory,
  37. "DescribeInstances": handleDescribeInstances,
  38. "__GetRegions": handleGetRegions,
  39. "__GetNamespaces": handleGetNamespaces,
  40. "__GetMetrics": handleGetMetrics,
  41. "__GetDimensions": handleGetDimensions,
  42. }
  43. }
  44. type cache struct {
  45. credential *credentials.Credentials
  46. expiration *time.Time
  47. }
  48. type CloudwatchDatasource struct {
  49. Profile string
  50. Region string
  51. AssumeRoleArn string
  52. Namespace string
  53. AccessKey string
  54. SecretKey string
  55. }
  56. var awsCredentialCache map[string]cache = make(map[string]cache)
  57. var credentialCacheLock sync.RWMutex
  58. func getCredentials(cwDatasource *CloudwatchDatasource) *credentials.Credentials {
  59. cacheKey := cwDatasource.Profile + ":" + cwDatasource.AssumeRoleArn
  60. credentialCacheLock.RLock()
  61. if _, ok := awsCredentialCache[cacheKey]; ok {
  62. if awsCredentialCache[cacheKey].expiration != nil &&
  63. (*awsCredentialCache[cacheKey].expiration).After(time.Now().UTC()) {
  64. result := awsCredentialCache[cacheKey].credential
  65. credentialCacheLock.RUnlock()
  66. return result
  67. }
  68. }
  69. credentialCacheLock.RUnlock()
  70. accessKeyId := ""
  71. secretAccessKey := ""
  72. sessionToken := ""
  73. var expiration *time.Time
  74. expiration = nil
  75. if strings.Index(cwDatasource.AssumeRoleArn, "arn:aws:iam:") == 0 {
  76. params := &sts.AssumeRoleInput{
  77. RoleArn: aws.String(cwDatasource.AssumeRoleArn),
  78. RoleSessionName: aws.String("GrafanaSession"),
  79. DurationSeconds: aws.Int64(900),
  80. }
  81. stsSess := session.New()
  82. stsCreds := credentials.NewChainCredentials(
  83. []credentials.Provider{
  84. &credentials.EnvProvider{},
  85. &credentials.SharedCredentialsProvider{Filename: "", Profile: cwDatasource.Profile},
  86. &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(stsSess), ExpiryWindow: 5 * time.Minute},
  87. })
  88. stsConfig := &aws.Config{
  89. Region: aws.String(cwDatasource.Region),
  90. Credentials: stsCreds,
  91. }
  92. svc := sts.New(session.New(stsConfig), stsConfig)
  93. resp, err := svc.AssumeRole(params)
  94. if err != nil {
  95. // ignore
  96. log.Error(3, "CloudWatch: Failed to assume role", err)
  97. }
  98. if resp.Credentials != nil {
  99. accessKeyId = *resp.Credentials.AccessKeyId
  100. secretAccessKey = *resp.Credentials.SecretAccessKey
  101. sessionToken = *resp.Credentials.SessionToken
  102. expiration = resp.Credentials.Expiration
  103. }
  104. }
  105. sess := session.New()
  106. creds := credentials.NewChainCredentials(
  107. []credentials.Provider{
  108. &credentials.StaticProvider{Value: credentials.Value{
  109. AccessKeyID: accessKeyId,
  110. SecretAccessKey: secretAccessKey,
  111. SessionToken: sessionToken,
  112. }},
  113. &credentials.EnvProvider{},
  114. &credentials.StaticProvider{Value: credentials.Value{
  115. AccessKeyID: cwDatasource.AccessKey,
  116. SecretAccessKey: cwDatasource.SecretKey,
  117. }},
  118. &credentials.SharedCredentialsProvider{Filename: "", Profile: cwDatasource.Profile},
  119. &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute},
  120. })
  121. credentialCacheLock.Lock()
  122. awsCredentialCache[cacheKey] = cache{
  123. credential: creds,
  124. expiration: expiration,
  125. }
  126. credentialCacheLock.Unlock()
  127. return creds
  128. }
  129. func getAwsConfig(req *cwRequest) *aws.Config {
  130. assumeRoleArn := req.DataSource.JsonData.Get("assumeRoleArn").MustString()
  131. accessKey := req.DataSource.JsonData.Get("accessKey").MustString()
  132. secretKey := req.DataSource.JsonData.Get("secretKey").MustString()
  133. cfg := &aws.Config{
  134. Region: aws.String(req.Region),
  135. Credentials: getCredentials(&CloudwatchDatasource{
  136. AccessKey: accessKey,
  137. SecretKey: secretKey,
  138. Region: req.Region,
  139. Profile: req.DataSource.Database,
  140. AssumeRoleArn: assumeRoleArn,
  141. }),
  142. }
  143. return cfg
  144. }
  145. func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) {
  146. cfg := getAwsConfig(req)
  147. svc := cloudwatch.New(session.New(cfg), cfg)
  148. reqParam := &struct {
  149. Parameters struct {
  150. Namespace string `json:"namespace"`
  151. MetricName string `json:"metricName"`
  152. Dimensions []*cloudwatch.Dimension `json:"dimensions"`
  153. Statistics []*string `json:"statistics"`
  154. StartTime int64 `json:"startTime"`
  155. EndTime int64 `json:"endTime"`
  156. Period int64 `json:"period"`
  157. } `json:"parameters"`
  158. }{}
  159. json.Unmarshal(req.Body, reqParam)
  160. params := &cloudwatch.GetMetricStatisticsInput{
  161. Namespace: aws.String(reqParam.Parameters.Namespace),
  162. MetricName: aws.String(reqParam.Parameters.MetricName),
  163. Dimensions: reqParam.Parameters.Dimensions,
  164. Statistics: reqParam.Parameters.Statistics,
  165. StartTime: aws.Time(time.Unix(reqParam.Parameters.StartTime, 0)),
  166. EndTime: aws.Time(time.Unix(reqParam.Parameters.EndTime, 0)),
  167. Period: aws.Int64(reqParam.Parameters.Period),
  168. }
  169. resp, err := svc.GetMetricStatistics(params)
  170. if err != nil {
  171. c.JsonApiErr(500, "Unable to call AWS API", err)
  172. return
  173. }
  174. c.JSON(200, resp)
  175. }
  176. func handleListMetrics(req *cwRequest, c *middleware.Context) {
  177. cfg := getAwsConfig(req)
  178. svc := cloudwatch.New(session.New(cfg), cfg)
  179. reqParam := &struct {
  180. Parameters struct {
  181. Namespace string `json:"namespace"`
  182. MetricName string `json:"metricName"`
  183. Dimensions []*cloudwatch.DimensionFilter `json:"dimensions"`
  184. } `json:"parameters"`
  185. }{}
  186. json.Unmarshal(req.Body, reqParam)
  187. params := &cloudwatch.ListMetricsInput{
  188. Namespace: aws.String(reqParam.Parameters.Namespace),
  189. MetricName: aws.String(reqParam.Parameters.MetricName),
  190. Dimensions: reqParam.Parameters.Dimensions,
  191. }
  192. var resp cloudwatch.ListMetricsOutput
  193. err := svc.ListMetricsPages(params,
  194. func(page *cloudwatch.ListMetricsOutput, lastPage bool) bool {
  195. metrics, _ := awsutil.ValuesAtPath(page, "Metrics")
  196. for _, metric := range metrics {
  197. resp.Metrics = append(resp.Metrics, metric.(*cloudwatch.Metric))
  198. }
  199. return !lastPage
  200. })
  201. if err != nil {
  202. c.JsonApiErr(500, "Unable to call AWS API", err)
  203. return
  204. }
  205. c.JSON(200, resp)
  206. }
  207. func handleDescribeAlarms(req *cwRequest, c *middleware.Context) {
  208. cfg := getAwsConfig(req)
  209. svc := cloudwatch.New(session.New(cfg), cfg)
  210. reqParam := &struct {
  211. Parameters struct {
  212. ActionPrefix string `json:"actionPrefix"`
  213. AlarmNamePrefix string `json:"alarmNamePrefix"`
  214. AlarmNames []*string `json:"alarmNames"`
  215. StateValue string `json:"stateValue"`
  216. } `json:"parameters"`
  217. }{}
  218. json.Unmarshal(req.Body, reqParam)
  219. params := &cloudwatch.DescribeAlarmsInput{
  220. MaxRecords: aws.Int64(100),
  221. }
  222. if reqParam.Parameters.ActionPrefix != "" {
  223. params.ActionPrefix = aws.String(reqParam.Parameters.ActionPrefix)
  224. }
  225. if reqParam.Parameters.AlarmNamePrefix != "" {
  226. params.AlarmNamePrefix = aws.String(reqParam.Parameters.AlarmNamePrefix)
  227. }
  228. if len(reqParam.Parameters.AlarmNames) != 0 {
  229. params.AlarmNames = reqParam.Parameters.AlarmNames
  230. }
  231. if reqParam.Parameters.StateValue != "" {
  232. params.StateValue = aws.String(reqParam.Parameters.StateValue)
  233. }
  234. resp, err := svc.DescribeAlarms(params)
  235. if err != nil {
  236. c.JsonApiErr(500, "Unable to call AWS API", err)
  237. return
  238. }
  239. c.JSON(200, resp)
  240. }
  241. func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) {
  242. cfg := getAwsConfig(req)
  243. svc := cloudwatch.New(session.New(cfg), cfg)
  244. reqParam := &struct {
  245. Parameters struct {
  246. Namespace string `json:"namespace"`
  247. MetricName string `json:"metricName"`
  248. Dimensions []*cloudwatch.Dimension `json:"dimensions"`
  249. Statistic string `json:"statistic"`
  250. Period int64 `json:"period"`
  251. } `json:"parameters"`
  252. }{}
  253. json.Unmarshal(req.Body, reqParam)
  254. params := &cloudwatch.DescribeAlarmsForMetricInput{
  255. Namespace: aws.String(reqParam.Parameters.Namespace),
  256. MetricName: aws.String(reqParam.Parameters.MetricName),
  257. Period: aws.Int64(reqParam.Parameters.Period),
  258. }
  259. if len(reqParam.Parameters.Dimensions) != 0 {
  260. params.Dimensions = reqParam.Parameters.Dimensions
  261. }
  262. if reqParam.Parameters.Statistic != "" {
  263. params.Statistic = aws.String(reqParam.Parameters.Statistic)
  264. }
  265. resp, err := svc.DescribeAlarmsForMetric(params)
  266. if err != nil {
  267. c.JsonApiErr(500, "Unable to call AWS API", err)
  268. return
  269. }
  270. c.JSON(200, resp)
  271. }
  272. func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) {
  273. cfg := getAwsConfig(req)
  274. svc := cloudwatch.New(session.New(cfg), cfg)
  275. reqParam := &struct {
  276. Parameters struct {
  277. AlarmName string `json:"alarmName"`
  278. HistoryItemType string `json:"historyItemType"`
  279. StartDate int64 `json:"startDate"`
  280. EndDate int64 `json:"endDate"`
  281. } `json:"parameters"`
  282. }{}
  283. json.Unmarshal(req.Body, reqParam)
  284. params := &cloudwatch.DescribeAlarmHistoryInput{
  285. AlarmName: aws.String(reqParam.Parameters.AlarmName),
  286. StartDate: aws.Time(time.Unix(reqParam.Parameters.StartDate, 0)),
  287. EndDate: aws.Time(time.Unix(reqParam.Parameters.EndDate, 0)),
  288. }
  289. if reqParam.Parameters.HistoryItemType != "" {
  290. params.HistoryItemType = aws.String(reqParam.Parameters.HistoryItemType)
  291. }
  292. resp, err := svc.DescribeAlarmHistory(params)
  293. if err != nil {
  294. c.JsonApiErr(500, "Unable to call AWS API", err)
  295. return
  296. }
  297. c.JSON(200, resp)
  298. }
  299. func handleDescribeInstances(req *cwRequest, c *middleware.Context) {
  300. cfg := getAwsConfig(req)
  301. svc := ec2.New(session.New(cfg), cfg)
  302. reqParam := &struct {
  303. Parameters struct {
  304. Filters []*ec2.Filter `json:"filters"`
  305. InstanceIds []*string `json:"instanceIds"`
  306. } `json:"parameters"`
  307. }{}
  308. json.Unmarshal(req.Body, reqParam)
  309. params := &ec2.DescribeInstancesInput{}
  310. if len(reqParam.Parameters.Filters) > 0 {
  311. params.Filters = reqParam.Parameters.Filters
  312. }
  313. if len(reqParam.Parameters.InstanceIds) > 0 {
  314. params.InstanceIds = reqParam.Parameters.InstanceIds
  315. }
  316. var resp ec2.DescribeInstancesOutput
  317. err := svc.DescribeInstancesPages(params,
  318. func(page *ec2.DescribeInstancesOutput, lastPage bool) bool {
  319. reservations, _ := awsutil.ValuesAtPath(page, "Reservations")
  320. for _, reservation := range reservations {
  321. resp.Reservations = append(resp.Reservations, reservation.(*ec2.Reservation))
  322. }
  323. return !lastPage
  324. })
  325. if err != nil {
  326. c.JsonApiErr(500, "Unable to call AWS API", err)
  327. return
  328. }
  329. c.JSON(200, resp)
  330. }
  331. func HandleRequest(c *middleware.Context, ds *m.DataSource) {
  332. var req cwRequest
  333. req.Body, _ = ioutil.ReadAll(c.Req.Request.Body)
  334. req.DataSource = ds
  335. json.Unmarshal(req.Body, &req)
  336. if handler, found := actionHandlers[req.Action]; !found {
  337. c.JsonApiErr(500, "Unexpected AWS Action", errors.New(req.Action))
  338. return
  339. } else {
  340. handler(&req, c)
  341. }
  342. }